Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -749,6 +749,11 @@ Dependencies<[SmartPtrModeling]>, Documentation; +def CPlusPlus11LockChecker : Checker<"CPlusPlus11Lock">, + HelpText<"Simple C++11 lock -> unlock checker">, + Dependencies<[PthreadLockBase]>, + Documentation; + } // end: "alpha.cplusplus" Index: clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -33,7 +33,9 @@ enum Kind { Destroyed, Locked, + SharedLocked, Unlocked, + SharedUnlocked, UntouchedAndPossiblyDestroyed, UnlockedAndPossiblyDestroyed } K; @@ -43,7 +45,9 @@ public: static LockState getLocked() { return LockState(Locked); } + static LockState getSharedLocked() { return LockState(SharedLocked); } static LockState getUnlocked() { return LockState(Unlocked); } + static LockState getSharedUnlocked() { return LockState(SharedUnlocked); } static LockState getDestroyed() { return LockState(Destroyed); } static LockState getUntouchedAndPossiblyDestroyed() { return LockState(UntouchedAndPossiblyDestroyed); @@ -55,7 +59,9 @@ bool operator==(const LockState &X) const { return K == X.K; } bool isLocked() const { return K == Locked; } + bool isSharedLocked() const { return K == SharedLocked; } bool isUnlocked() const { return K == Unlocked; } + bool isSharedUnlocked() const { return K == SharedUnlocked; } bool isDestroyed() const { return K == Destroyed; } bool isUntouchedAndPossiblyDestroyed() const { return K == UntouchedAndPossiblyDestroyed; @@ -70,11 +76,18 @@ class PthreadLockChecker : public Checker { public: - enum LockingSemantics { NotApplicable = 0, PthreadSemantics, XNUSemantics }; + enum LockingSemantics { + NotApplicable = 0, + PthreadSemantics, + XNUSemantics, + CPlusPlusSemantics, + CPlusPlusSharedSemantics, + }; enum CheckerKind { CK_PthreadLockChecker, CK_FuchsiaLockChecker, CK_C11LockChecker, + CK_CPlusPlus11LockChecker, CK_NumCheckKinds }; DefaultBool ChecksEnabled[CK_NumCheckKinds]; @@ -83,7 +96,7 @@ private: typedef void (PthreadLockChecker::*FnCheck)(const CallEvent &Call, CheckerContext &C, - CheckerKind checkkind) const; + CheckerKind CheckKind) const; CallDescriptionMap PThreadCallbacks = { // Init. {{"pthread_mutex_init", 2}, &PthreadLockChecker::InitAnyLock}, @@ -164,49 +177,120 @@ {{"mtx_destroy", 1}, &PthreadLockChecker::DestroyPthreadLock}, }; + CallDescriptionMap CPlusPlus11Callbacks = { + + // Acquire. + {{{"std", "mutex", "lock"}, 0, 0}, + &PthreadLockChecker::AcquireCPlusPlus11Lock}, + {{{"std", "timed_mutex", "lock"}, 0, 0}, + &PthreadLockChecker::AcquireCPlusPlus11Lock}, + {{{"std", "recursive_mutex", "lock"}, 0, 0}, + &PthreadLockChecker::AcquireCPlusPlus11Lock}, + {{{"std", "recursive_timed_mutex", "lock"}, 0, 0}, + &PthreadLockChecker::AcquireCPlusPlus11Lock}, + {{{"std", "shared_mutex", "lock"}, 0, 0}, + &PthreadLockChecker::AcquireCPlusPlus11Lock}, + {{{"std", "shared_mutex", "lock_shared"}, 0, 0}, + &PthreadLockChecker::AcquireCPlusPlus11LockShared}, + {{{"std", "shared_timed_mutex", "lock"}, 0, 0}, + &PthreadLockChecker::AcquireCPlusPlus11Lock}, + {{{"std", "shared_timed_mutex", "lock_shared"}, 0, 0}, + &PthreadLockChecker::AcquireCPlusPlus11LockShared}, + + // Try. + {{{"std", "mutex", "try_lock"}, 0, 0}, + &PthreadLockChecker::TryCPlusPlus11Lock}, + {{{"std", "timed_mutex", "try_lock"}, 0, 0}, + &PthreadLockChecker::TryCPlusPlus11Lock}, + {{{"std", "recursive_mutex", "try_lock"}, 0, 0}, + &PthreadLockChecker::TryCPlusPlus11Lock}, + {{{"std", "recursive_timed_mutex", "try_lock"}, 0, 0}, + &PthreadLockChecker::TryCPlusPlus11Lock}, + {{{"std", "shared_mutex", "try_lock"}, 0, 0}, + &PthreadLockChecker::TryCPlusPlus11Lock}, + {{{"std", "shared_mutex", "try_lock_shared"}, 0, 0}, + &PthreadLockChecker::TryCPlusPlus11LockShared}, + {{{"std", "shared_timed_mutex", "try_lock"}, 0, 0}, + &PthreadLockChecker::TryCPlusPlus11Lock}, + {{{"std", "shared_timed_mutex", "try_lock_shared"}, 0, 0}, + &PthreadLockChecker::TryCPlusPlus11LockShared}, + + // Release. + {{{"std", "mutex", "unlock"}, 0, 0}, + &PthreadLockChecker::ReleaseCPlusPlus11Lock}, + {{{"std", "timed_mutex", "unlock"}, 0, 0}, + &PthreadLockChecker::ReleaseCPlusPlus11Lock}, + {{{"std", "recursive_mutex", "unlock"}, 0, 0}, + &PthreadLockChecker::ReleaseCPlusPlus11Lock}, + {{{"std", "recursive_timed_mutex", "unlock"}, 0, 0}, + &PthreadLockChecker::ReleaseCPlusPlus11Lock}, + {{{"std", "shared_mutex", "unlock"}, 0, 0}, + &PthreadLockChecker::ReleaseCPlusPlus11Lock}, + {{{"std", "shared_mutex", "unlock_shared"}, 0, 0}, + &PthreadLockChecker::ReleaseCPlusPlus11LockShared}, + {{{"std", "shared_timed_mutex", "unlock"}, 0, 0}, + &PthreadLockChecker::ReleaseCPlusPlus11Lock}, + {{{"std", "shared_timed_mutex", "unlock_shared"}, 0, 0}, + &PthreadLockChecker::ReleaseCPlusPlus11LockShared}, + }; + ProgramStateRef resolvePossiblyDestroyedMutex(ProgramStateRef state, const MemRegion *lockR, const SymbolRef *sym) const; - void reportUseDestroyedBug(const CallEvent &Call, CheckerContext &C, - unsigned ArgNo, CheckerKind checkKind) const; + void reportBug(CheckerContext &C, std::unique_ptr BT[], + const Expr *MtxExpr, CheckerKind CheckKind, + StringRef TextReport) const; // Init. void InitAnyLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkkind) const; + CheckerKind CheckKind) const; void InitLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, - SVal Lock, CheckerKind checkkind) const; + SVal Lock, CheckerKind CheckKind) const; // Lock, Try-lock. void AcquirePthreadLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkkind) const; + CheckerKind CheckKind) const; void AcquireXNULock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkkind) const; + CheckerKind CheckKind) const; + void AcquireCPlusPlus11Lock(const CallEvent &Call, CheckerContext &C, + CheckerKind CheckKind) const; + void AcquireCPlusPlus11LockShared(const CallEvent &Call, CheckerContext &C, + CheckerKind CheckKind) const; void TryPthreadLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkkind) const; + CheckerKind CheckKind) const; void TryXNULock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkkind) const; + CheckerKind CheckKind) const; void TryFuchsiaLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkkind) const; + CheckerKind CheckKind) const; void TryC11Lock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkkind) const; - void AcquireLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, - SVal lock, bool isTryLock, LockingSemantics semantics, - CheckerKind checkkind) const; + CheckerKind CheckKind) const; + void TryCPlusPlus11Lock(const CallEvent &Call, CheckerContext &C, + CheckerKind CheckKind) const; + void TryCPlusPlus11LockShared(const CallEvent &Call, CheckerContext &C, + CheckerKind CheckKind) const; + void AcquireLockAux(const CallEvent &Call, CheckerContext &C, + const Expr *MtxExpr, SVal MtxVal, bool IsTryLock, + LockingSemantics semantics, CheckerKind CheckKind) const; // Release. void ReleaseAnyLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkkind) const; - void ReleaseLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, - SVal lock, CheckerKind checkkind) const; + CheckerKind CheckKind) const; + void ReleaseCPlusPlus11Lock(const CallEvent &Call, CheckerContext &C, + CheckerKind CheckKind) const; + void ReleaseCPlusPlus11LockShared(const CallEvent &Call, CheckerContext &C, + CheckerKind CheckKind) const; + void ReleaseLockAux(const CallEvent &Call, CheckerContext &C, + const Expr *MtxExpr, SVal MtxVal, + LockingSemantics semantics, CheckerKind CheckKind) const; // Destroy. void DestroyPthreadLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkkind) const; + CheckerKind CheckKind) const; void DestroyXNULock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkkind) const; + CheckerKind CheckKind) const; void DestroyLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, SVal Lock, LockingSemantics semantics, - CheckerKind checkkind) const; + CheckerKind CheckKind) const; public: void checkPostCall(const CallEvent &Call, CheckerContext &C) const; @@ -225,20 +309,23 @@ mutable std::unique_ptr BT_destroylock[CK_NumCheckKinds]; mutable std::unique_ptr BT_initlock[CK_NumCheckKinds]; mutable std::unique_ptr BT_lor[CK_NumCheckKinds]; + mutable std::unique_ptr BT_shared[CK_NumCheckKinds]; - void initBugType(CheckerKind checkKind) const { - if (BT_doublelock[checkKind]) + void initBugType(CheckerKind CheckKind) const { + if (BT_doublelock[CheckKind]) return; - BT_doublelock[checkKind].reset( - new BugType{CheckNames[checkKind], "Double locking", "Lock checker"}); - BT_doubleunlock[checkKind].reset( - new BugType{CheckNames[checkKind], "Double unlocking", "Lock checker"}); - BT_destroylock[checkKind].reset(new BugType{ - CheckNames[checkKind], "Use destroyed lock", "Lock checker"}); - BT_initlock[checkKind].reset(new BugType{ - CheckNames[checkKind], "Init invalid lock", "Lock checker"}); - BT_lor[checkKind].reset(new BugType{CheckNames[checkKind], + BT_doublelock[CheckKind].reset( + new BugType{CheckNames[CheckKind], "Double locking", "Lock checker"}); + BT_doubleunlock[CheckKind].reset( + new BugType{CheckNames[CheckKind], "Double unlocking", "Lock checker"}); + BT_destroylock[CheckKind].reset(new BugType{ + CheckNames[CheckKind], "Use destroyed lock", "Lock checker"}); + BT_initlock[CheckKind].reset(new BugType{ + CheckNames[CheckKind], "Init invalid lock", "Lock checker"}); + BT_lor[CheckKind].reset(new BugType{CheckNames[CheckKind], "Lock order reversal", "Lock checker"}); + BT_shared[CheckKind].reset(new BugType{ + CheckNames[CheckKind], "Shared semantics misuse", "Lock checker"}); } }; } // end anonymous namespace @@ -260,7 +347,7 @@ // with exactly one identifier? // FIXME: Try to handle cases when the implementation was inlined rather // than just giving up. - if (!Call.isGlobalCFunction() || C.wasInlined) + if (C.wasInlined) return; if (const FnCheck *Callback = PThreadCallbacks.lookup(Call)) @@ -269,6 +356,8 @@ (this->**Callback)(Call, C, CK_FuchsiaLockChecker); else if (const FnCheck *Callback = C11Callbacks.lookup(Call)) (this->**Callback)(Call, C, CK_C11LockChecker); + else if (const FnCheck *Callback = CPlusPlus11Callbacks.lookup(Call)) + (this->**Callback)(Call, C, CK_CPlusPlus11LockChecker); } // When a lock is destroyed, in some semantics(like PthreadSemantics) we are not @@ -315,6 +404,8 @@ I.first->dumpToStream(Out); if (I.second.isLocked()) Out << ": locked"; + else if (I.second.isSharedLocked()) + Out << ": shared locked"; else if (I.second.isUnlocked()) Out << ": unlocked"; else if (I.second.isDestroyed()) @@ -341,53 +432,92 @@ void PthreadLockChecker::AcquirePthreadLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkKind) const { - AcquireLockAux(Call, C, 0, Call.getArgSVal(0), false, PthreadSemantics, - checkKind); + CheckerKind CheckKind) const { + AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), false, + PthreadSemantics, CheckKind); } void PthreadLockChecker::AcquireXNULock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkKind) const { - AcquireLockAux(Call, C, 0, Call.getArgSVal(0), false, XNUSemantics, - checkKind); + CheckerKind CheckKind) const { + AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), false, + XNUSemantics, CheckKind); +} + +void PthreadLockChecker::AcquireCPlusPlus11Lock(const CallEvent &Call, + CheckerContext &C, + CheckerKind CheckKind) const { + auto MemberCall = cast(&Call); + auto ThisExpr = MemberCall->getCXXThisExpr(); + auto ThisVal = MemberCall->getCXXThisVal(); + AcquireLockAux(Call, C, ThisExpr, ThisVal, false, CPlusPlusSemantics, + CheckKind); +} + +void PthreadLockChecker::AcquireCPlusPlus11LockShared( + const CallEvent &Call, CheckerContext &C, CheckerKind CheckKind) const { + auto MemberCall = cast(&Call); + auto ThisExpr = MemberCall->getCXXThisExpr(); + auto ThisVal = MemberCall->getCXXThisVal(); + AcquireLockAux(Call, C, ThisExpr, ThisVal, false, CPlusPlusSharedSemantics, + CheckKind); } void PthreadLockChecker::TryPthreadLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkKind) const { - AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, - checkKind); + CheckerKind CheckKind) const { + AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), true, + PthreadSemantics, CheckKind); } void PthreadLockChecker::TryXNULock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkKind) const { - AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, - checkKind); + CheckerKind CheckKind) const { + AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), true, + PthreadSemantics, CheckKind); } void PthreadLockChecker::TryFuchsiaLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkKind) const { - AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, - checkKind); + CheckerKind CheckKind) const { + AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), true, + PthreadSemantics, CheckKind); } void PthreadLockChecker::TryC11Lock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkKind) const { - AcquireLockAux(Call, C, 0, Call.getArgSVal(0), true, PthreadSemantics, - checkKind); + CheckerKind CheckKind) const { + AcquireLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), true, + PthreadSemantics, CheckKind); +} + +void PthreadLockChecker::TryCPlusPlus11Lock(const CallEvent &Call, + CheckerContext &C, + CheckerKind CheckKind) const { + auto MemberCall = cast(&Call); + auto ThisExpr = MemberCall->getCXXThisExpr(); + auto ThisVal = MemberCall->getCXXThisVal(); + AcquireLockAux(Call, C, ThisExpr, ThisVal, true, CPlusPlusSemantics, + CheckKind); +} + +void PthreadLockChecker::TryCPlusPlus11LockShared(const CallEvent &Call, + CheckerContext &C, + CheckerKind CheckKind) const { + auto MemberCall = cast(&Call); + auto ThisExpr = MemberCall->getCXXThisExpr(); + auto ThisVal = MemberCall->getCXXThisVal(); + AcquireLockAux(Call, C, ThisExpr, ThisVal, true, CPlusPlusSharedSemantics, + CheckKind); } void PthreadLockChecker::AcquireLockAux(const CallEvent &Call, - CheckerContext &C, unsigned ArgNo, - SVal lock, bool isTryLock, - enum LockingSemantics semantics, - CheckerKind checkKind) const { - if (!ChecksEnabled[checkKind]) + CheckerContext &C, const Expr *MtxExpr, + SVal MtxVal, bool IsTryLock, + LockingSemantics semantics, + CheckerKind CheckKind) const { + if (!ChecksEnabled[CheckKind]) return; - const MemRegion *lockR = lock.getAsRegion(); + const MemRegion *lockR = MtxVal.getAsRegion(); if (!lockR) return; @@ -397,39 +527,36 @@ state = resolvePossiblyDestroyedMutex(state, lockR, sym); if (const LockState *LState = state->get(lockR)) { - if (LState->isLocked()) { - ExplodedNode *N = C.generateErrorNode(); - if (!N) - return; - initBugType(checkKind); - auto report = std::make_unique( - *BT_doublelock[checkKind], "This lock has already been acquired", N); - report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); - C.emitReport(std::move(report)); + if (LState->isLocked() || LState->isSharedLocked()) { + reportBug(C, BT_doublelock, MtxExpr, CheckKind, + "This lock has already been acquired"); return; } else if (LState->isDestroyed()) { - reportUseDestroyedBug(Call, C, ArgNo, checkKind); + reportBug(C, BT_destroylock, MtxExpr, CheckKind, + "This lock has already been destroyed"); return; } } - ProgramStateRef lockSucc = state; - if (isTryLock) { + ProgramStateRef LockSucc = state; + if (IsTryLock) { // Bifurcate the state, and allow a mode where the lock acquisition fails. SVal RetVal = Call.getReturnValue(); if (auto DefinedRetVal = RetVal.getAs()) { ProgramStateRef lockFail; switch (semantics) { case PthreadSemantics: - std::tie(lockFail, lockSucc) = state->assume(*DefinedRetVal); + std::tie(lockFail, LockSucc) = state->assume(*DefinedRetVal); break; + case CPlusPlusSemantics: + case CPlusPlusSharedSemantics: case XNUSemantics: - std::tie(lockSucc, lockFail) = state->assume(*DefinedRetVal); + std::tie(LockSucc, lockFail) = state->assume(*DefinedRetVal); break; default: llvm_unreachable("Unknown tryLock locking semantics"); } - assert(lockFail && lockSucc); + assert(lockFail && LockSucc); C.addTransition(lockFail); } // We might want to handle the case when the mutex lock function was inlined @@ -440,37 +567,61 @@ if (auto DefinedRetVal = RetVal.getAs()) { // FIXME: If the lock function was inlined and returned true, // we need to behave sanely - at least generate sink. - lockSucc = state->assume(*DefinedRetVal, false); - assert(lockSucc); + LockSucc = state->assume(*DefinedRetVal, false); + assert(LockSucc); } // We might want to handle the case when the mutex lock function was inlined // and returned an Unknown or Undefined value. } else { - // XNU locking semantics return void on non-try locks - assert((semantics == XNUSemantics) && "Unknown locking semantics"); - lockSucc = state; + // XNU and C++ locking semantics return void on non-try locks + assert((semantics == CPlusPlusSharedSemantics || + semantics == CPlusPlusSemantics || semantics == XNUSemantics) && + "Unknown locking semantics"); + LockSucc = state; } // Record that the lock was acquired. - lockSucc = lockSucc->add(lockR); - lockSucc = lockSucc->set(lockR, LockState::getLocked()); - C.addTransition(lockSucc); + LockSucc = LockSucc->add(lockR); + auto State = (semantics == CPlusPlusSharedSemantics) + ? LockState::getSharedLocked() + : LockState::getLocked(); + LockSucc = LockSucc->set(lockR, State); + C.addTransition(LockSucc); } void PthreadLockChecker::ReleaseAnyLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkKind) const { - ReleaseLockAux(Call, C, 0, Call.getArgSVal(0), checkKind); + CheckerKind CheckKind) const { + ReleaseLockAux(Call, C, Call.getArgExpr(0), Call.getArgSVal(0), NotApplicable, + CheckKind); +} + +void PthreadLockChecker::ReleaseCPlusPlus11Lock(const CallEvent &Call, + CheckerContext &C, + CheckerKind CheckKind) const { + auto MemberCall = cast(&Call); + auto ThisExpr = MemberCall->getCXXThisExpr(); + auto ThisVal = MemberCall->getCXXThisVal(); + ReleaseLockAux(Call, C, ThisExpr, ThisVal, CPlusPlusSemantics, CheckKind); +} + +void PthreadLockChecker::ReleaseCPlusPlus11LockShared( + const CallEvent &Call, CheckerContext &C, CheckerKind CheckKind) const { + auto MemberCall = cast(&Call); + auto ThisExpr = MemberCall->getCXXThisExpr(); + auto ThisVal = MemberCall->getCXXThisVal(); + ReleaseLockAux(Call, C, ThisExpr, ThisVal, CPlusPlusSharedSemantics, + CheckKind); } void PthreadLockChecker::ReleaseLockAux(const CallEvent &Call, - CheckerContext &C, unsigned ArgNo, - SVal lock, - CheckerKind checkKind) const { - if (!ChecksEnabled[checkKind]) + CheckerContext &C, const Expr *MtxExpr, + SVal MtxVal, LockingSemantics semantics, + CheckerKind CheckKind) const { + if (!ChecksEnabled[CheckKind]) return; - const MemRegion *lockR = lock.getAsRegion(); + const MemRegion *lockR = MtxVal.getAsRegion(); if (!lockR) return; @@ -480,20 +631,28 @@ state = resolvePossiblyDestroyedMutex(state, lockR, sym); if (const LockState *LState = state->get(lockR)) { - if (LState->isUnlocked()) { - ExplodedNode *N = C.generateErrorNode(); - if (!N) - return; - initBugType(checkKind); - auto Report = std::make_unique( - *BT_doubleunlock[checkKind], "This lock has already been unlocked", - N); - Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); - C.emitReport(std::move(Report)); + if (LState->isUnlocked() || LState->isSharedUnlocked()) { + reportBug(C, BT_doubleunlock, MtxExpr, CheckKind, + "This lock has already been unlocked"); return; } else if (LState->isDestroyed()) { - reportUseDestroyedBug(Call, C, ArgNo, checkKind); + reportBug(C, BT_destroylock, MtxExpr, CheckKind, + "This lock has already been destroyed"); return; + } else if (LState->isLocked()) { + if (semantics == CPlusPlusSharedSemantics) { + reportBug( + C, BT_shared, MtxExpr, CheckKind, + "This lock has been locked exclusively, but shared unlock used"); + return; + } + } else if (LState->isSharedLocked()) { + if (semantics == CPlusPlusSemantics) { + reportBug( + C, BT_shared, MtxExpr, CheckKind, + "This lock has been locked as shared, but exclusive unlock used"); + return; + } } } @@ -502,45 +661,39 @@ if (!LS.isEmpty()) { const MemRegion *firstLockR = LS.getHead(); if (firstLockR != lockR) { - ExplodedNode *N = C.generateErrorNode(); - if (!N) - return; - initBugType(checkKind); - auto report = std::make_unique( - *BT_lor[checkKind], - "This was not the most recently acquired lock. Possible " - "lock order reversal", - N); - report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); - C.emitReport(std::move(report)); + reportBug(C, BT_lor, MtxExpr, CheckKind, + "This was not the most recently acquired lock. Possible lock " + "order reversal"); return; } // Record that the lock was released. state = state->set(LS.getTail()); } - state = state->set(lockR, LockState::getUnlocked()); + auto State = (semantics == CPlusPlusSharedSemantics) + ? LockState::getSharedUnlocked() + : LockState::getUnlocked(); + state = state->set(lockR, State); C.addTransition(state); } void PthreadLockChecker::DestroyPthreadLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkKind) const { - DestroyLockAux(Call, C, 0, Call.getArgSVal(0), PthreadSemantics, checkKind); + CheckerKind CheckKind) const { + DestroyLockAux(Call, C, 0, Call.getArgSVal(0), PthreadSemantics, CheckKind); } void PthreadLockChecker::DestroyXNULock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkKind) const { - DestroyLockAux(Call, C, 0, Call.getArgSVal(0), XNUSemantics, checkKind); + CheckerKind CheckKind) const { + DestroyLockAux(Call, C, 0, Call.getArgSVal(0), XNUSemantics, CheckKind); } void PthreadLockChecker::DestroyLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, - SVal Lock, - enum LockingSemantics semantics, - CheckerKind checkKind) const { - if (!ChecksEnabled[checkKind]) + SVal Lock, LockingSemantics semantics, + CheckerKind CheckKind) const { + if (!ChecksEnabled[CheckKind]) return; const MemRegion *LockR = Lock.getAsRegion(); @@ -581,33 +734,23 @@ return; } } - StringRef Message; - if (LState->isLocked()) { - Message = "This lock is still locked"; - } else { - Message = "This lock has already been destroyed"; - } + StringRef Message = LState->isLocked() + ? "This lock is still locked" + : "This lock has already been destroyed"; - ExplodedNode *N = C.generateErrorNode(); - if (!N) - return; - initBugType(checkKind); - auto Report = std::make_unique( - *BT_destroylock[checkKind], Message, N); - Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); - C.emitReport(std::move(Report)); + reportBug(C, BT_destroylock, Call.getArgExpr(ArgNo), CheckKind, Message); } void PthreadLockChecker::InitAnyLock(const CallEvent &Call, CheckerContext &C, - CheckerKind checkKind) const { - InitLockAux(Call, C, 0, Call.getArgSVal(0), checkKind); + CheckerKind CheckKind) const { + InitLockAux(Call, C, 0, Call.getArgSVal(0), CheckKind); } void PthreadLockChecker::InitLockAux(const CallEvent &Call, CheckerContext &C, unsigned ArgNo, SVal Lock, - CheckerKind checkKind) const { - if (!ChecksEnabled[checkKind]) + CheckerKind CheckKind) const { + if (!ChecksEnabled[CheckKind]) return; const MemRegion *LockR = Lock.getAsRegion(); @@ -627,35 +770,24 @@ return; } - StringRef Message; + StringRef Message = LState->isLocked() + ? "This lock is still being held" + : "This lock has already been initialized"; - if (LState->isLocked()) { - Message = "This lock is still being held"; - } else { - Message = "This lock has already been initialized"; - } - - ExplodedNode *N = C.generateErrorNode(); - if (!N) - return; - initBugType(checkKind); - auto Report = std::make_unique( - *BT_initlock[checkKind], Message, N); - Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); - C.emitReport(std::move(Report)); + reportBug(C, BT_initlock, Call.getArgExpr(ArgNo), CheckKind, Message); } -void PthreadLockChecker::reportUseDestroyedBug(const CallEvent &Call, - CheckerContext &C, - unsigned ArgNo, - CheckerKind checkKind) const { +void PthreadLockChecker::reportBug(CheckerContext &C, + std::unique_ptr BT[], + const Expr *MtxExpr, CheckerKind CheckKind, + StringRef Desc) const { ExplodedNode *N = C.generateErrorNode(); if (!N) return; - initBugType(checkKind); - auto Report = std::make_unique( - *BT_destroylock[checkKind], "This lock has already been destroyed", N); - Report->addRange(Call.getArgExpr(ArgNo)->getSourceRange()); + initBugType(CheckKind); + auto Report = + std::make_unique(*BT[CheckKind], Desc, N); + Report->addRange(MtxExpr->getSourceRange()); C.emitReport(std::move(Report)); } @@ -691,10 +823,10 @@ const CallEvent *Call) const { bool IsLibraryFunction = false; - if (Call && Call->isGlobalCFunction()) { + if (Call) { // Avoid invalidating mutex state when a known supported function is called. if (PThreadCallbacks.lookup(*Call) || FuchsiaCallbacks.lookup(*Call) || - C11Callbacks.lookup(*Call)) + C11Callbacks.lookup(*Call) || CPlusPlus11Callbacks.lookup(*Call)) return State; if (Call->isInSystemHeader()) @@ -740,3 +872,4 @@ REGISTER_CHECKER(PthreadLockChecker) REGISTER_CHECKER(FuchsiaLockChecker) REGISTER_CHECKER(C11LockChecker) +REGISTER_CHECKER(CPlusPlus11LockChecker) Index: clang/test/Analysis/Checkers/CPlusPlus11LockChecker.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/Checkers/CPlusPlus11LockChecker.cpp @@ -0,0 +1,902 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.CPlusPlus11Lock -verify %s + +namespace std { + +struct mutex { + void lock(); + bool try_lock(); + void unlock(); +}; + +struct timed_mutex { + void lock(); + bool try_lock(); + void unlock(); +}; + +struct recursive_mutex { + void lock(); + bool try_lock(); + void unlock(); +}; + +struct recursive_timed_mutex { + void lock(); + bool try_lock(); + void unlock(); +}; + +struct shared_mutex { + void lock(); + bool try_lock(); + void unlock(); + void lock_shared(); + bool try_lock_shared(); + void unlock_shared(); +}; + +struct shared_timed_mutex { + void lock(); + bool try_lock(); + void unlock(); + void lock_shared(); + bool try_lock_shared(); + void unlock_shared(); +}; + +template +struct lock_guard { + T &t; + lock_guard(T &m) : t(m) { + t.lock(); + } + ~lock_guard() { + t.unlock(); + } +}; + +template +struct shared_lock { + T &t; + shared_lock(T &m) : t(m) { + t.lock_shared(); + } + ~shared_lock() { + t.unlock_shared(); + } +}; +} // namespace std + +std::mutex m1; +std::mutex m2; + +// mutex ok + +void m_ok1() { + m1.lock(); // no-warning +} + +void m_ok2() { + m1.unlock(); // no-warning +} + +void m_ok3() { + m1.lock(); // no-warning + m1.unlock(); // no-warning +} + +void m_ok4() { + m1.lock(); // no-warning + m1.unlock(); // no-warning + m1.lock(); // no-warning + m1.unlock(); // no-warning +} + +void m_ok5() { + m1.lock(); // no-warning + m1.unlock(); // no-warning + m2.lock(); // no-warning + m2.unlock(); // no-warning +} + +void m_ok6(void) { + m1.lock(); // no-warning + m2.lock(); // no-warning + m2.unlock(); // no-warning + m1.unlock(); // no-warning +} + +void m_ok7(void) { + if (m1.try_lock()) // no-warning + m1.unlock(); // no-warning +} + +void m_ok8(void) { + m1.unlock(); // no-warning + if (m1.try_lock()) // no-warning + m1.unlock(); // no-warning +} + +void m_ok9(void) { + if (!m1.try_lock()) // no-warning + m1.lock(); // no-warning + m1.unlock(); // no-warning +} + +void m_ok10() { + std::lock_guard gl(m1); // no-warning +} + +// mutex bad + +void m_bad1() { + m1.lock(); // no-warning + m1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void m_bad2() { + m1.lock(); // no-warning + m1.unlock(); // no-warning + m1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void m_bad3() { + m1.lock(); // no-warning + m2.lock(); // no-warning + m1.unlock(); // expected-warning {{This was not the most recently acquired lock. Possible lock order reversal}} + m2.unlock(); // no-warning +} + +void m_bad5() { + while (true) + m1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void m_bad6() { + while (true) + m1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void m_bad7() { + if (m1.try_lock()) // no-warning + m1.lock(); // expected-warning{{This lock has already been acquired}} +} + +std::timed_mutex tm1; +std::timed_mutex tm2; + +// timed_mutex ok + +void tm_ok1() { + tm1.lock(); // no-warning +} + +void tm_ok2() { + tm1.unlock(); // no-warning +} + +void tm_ok3() { + tm1.lock(); // no-warning + tm1.unlock(); // no-warning +} + +void tm_ok4() { + tm1.lock(); // no-warning + tm1.unlock(); // no-warning + tm1.lock(); // no-warning + tm1.unlock(); // no-warning +} + +void tm_ok5() { + tm1.lock(); // no-warning + tm1.unlock(); // no-warning + tm2.lock(); // no-warning + tm2.unlock(); // no-warning +} + +void tm_ok6(void) { + tm1.lock(); // no-warning + tm2.lock(); // no-warning + tm2.unlock(); // no-warning + tm1.unlock(); // no-warning +} + +void tm_ok7(void) { + if (tm1.try_lock()) // no-warning + tm1.unlock(); // no-warning +} + +void tm_ok8(void) { + tm1.unlock(); // no-warning + if (tm1.try_lock()) // no-warning + tm1.unlock(); // no-warning +} + +void tm_ok9(void) { + if (!tm1.try_lock()) // no-warning + tm1.lock(); // no-warning + tm1.unlock(); // no-warning +} + +void tm_ok10() { + std::lock_guard gl(tm1); // no-warning +} + +// timed_mutex bad + +void tm_bad1() { + tm1.lock(); // no-warning + tm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void tm_bad2() { + tm1.lock(); // no-warning + tm1.unlock(); // no-warning + tm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void tm_bad3() { + tm1.lock(); // no-warning + tm2.lock(); // no-warning + tm1.unlock(); // expected-warning {{This was not the most recently acquired lock. Possible lock order reversal}} + tm2.unlock(); // no-warning +} + +void tm_bad5() { + while (true) + tm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void tm_bad6() { + while (true) + tm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void tm_bad7() { + if (tm1.try_lock()) // no-warning + tm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +std::recursive_mutex rm1; +std::recursive_mutex rm2; + +// recursive_mutex ok + +void rm_ok1() { + rm1.lock(); // no-warning +} + +void rm_ok2() { + rm1.unlock(); // no-warning +} + +void rm_ok3() { + rm1.lock(); // no-warning + rm1.unlock(); // no-warning +} + +void rm_ok4() { + rm1.lock(); // no-warning + rm1.unlock(); // no-warning + rm1.lock(); // no-warning + rm1.unlock(); // no-warning +} + +void rm_ok5() { + rm1.lock(); // no-warning + rm1.unlock(); // no-warning + rm2.lock(); // no-warning + rm2.unlock(); // no-warning +} + +void rm_ok6(void) { + rm1.lock(); // no-warning + rm2.lock(); // no-warning + rm2.unlock(); // no-warning + rm1.unlock(); // no-warning +} + +void rm_ok7(void) { + if (rm1.try_lock()) // no-warning + rm1.unlock(); // no-warning +} + +void rm_ok8(void) { + rm1.unlock(); // no-warning + if (rm1.try_lock()) // no-warning + rm1.unlock(); // no-warning +} + +void rm_ok9(void) { + if (!rm1.try_lock()) // no-warning + rm1.lock(); // no-warning + rm1.unlock(); // no-warning +} + +void rm_ok10() { + std::lock_guard gl(rm1); // no-warning +} + +// recursive_mutex bad + +void rm_bad1() { + rm1.lock(); // no-warning + rm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void rm_bad2() { + rm1.lock(); // no-warning + rm1.unlock(); // no-warning + rm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void rm_bad3() { + rm1.lock(); // no-warning + rm2.lock(); // no-warning + rm1.unlock(); // expected-warning {{This was not the most recently acquired lock. Possible lock order reversal}} + rm2.unlock(); // no-warning +} + +void rm_bad5() { + while (true) + rm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void rm_bad6() { + while (true) + rm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void rm_bad7() { + if (rm1.try_lock()) // no-warning + rm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +std::recursive_timed_mutex rtm1; +std::recursive_timed_mutex rtm2; + +// recursive_timed_mutex ok + +void rtm_ok1() { + rtm1.lock(); // no-warning +} + +void rtm_ok2() { + rtm1.unlock(); // no-warning +} + +void rtm_ok3() { + rtm1.lock(); // no-warning + rtm1.unlock(); // no-warning +} + +void rtm_ok4() { + rtm1.lock(); // no-warning + rtm1.unlock(); // no-warning + rtm1.lock(); // no-warning + rtm1.unlock(); // no-warning +} + +void rtm_ok5() { + rtm1.lock(); // no-warning + rtm1.unlock(); // no-warning + rtm2.lock(); // no-warning + rtm2.unlock(); // no-warning +} + +void rtm_ok6(void) { + rtm1.lock(); // no-warning + rtm2.lock(); // no-warning + rtm2.unlock(); // no-warning + rtm1.unlock(); // no-warning +} + +void rtm_ok7(void) { + if (rtm1.try_lock()) // no-warning + rtm1.unlock(); // no-warning +} + +void rtm_ok8(void) { + rtm1.unlock(); // no-warning + if (rtm1.try_lock()) // no-warning + rtm1.unlock(); // no-warning +} + +void rtm_ok9(void) { + if (!rtm1.try_lock()) // no-warning + rtm1.lock(); // no-warning + rtm1.unlock(); // no-warning +} + +void rtm_ok10() { + std::lock_guard gl(rtm1); // no-warning +} + +// recursive_timed_mutex bad + +void rtm_bad1() { + rtm1.lock(); // no-warning + rtm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void rtm_bad2() { + rtm1.lock(); // no-warning + rtm1.unlock(); // no-warning + rtm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void rtm_bad3() { + rtm1.lock(); // no-warning + rtm2.lock(); // no-warning + rtm1.unlock(); // expected-warning {{This was not the most recently acquired lock. Possible lock order reversal}} + rtm2.unlock(); // no-warning +} + +void rtm_bad5() { + while (true) + rtm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void rtm_bad6() { + while (true) + rtm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void rtm_bad7() { + if (rtm1.try_lock()) // no-warning + rtm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +std::shared_mutex sm1; +std::shared_mutex sm2; + +// shared_mutex ok + +void sm_ok1() { + sm1.lock(); // no-warning +} + +void sm_ok2() { + sm1.unlock(); // no-warning +} + +void sm_ok3() { + sm1.lock(); // no-warning + sm1.unlock(); // no-warning +} + +void sm_ok4() { + sm1.lock(); // no-warning + sm1.unlock(); // no-warning + sm1.lock(); // no-warning + sm1.unlock(); // no-warning +} + +void sm_ok5() { + sm1.lock(); // no-warning + sm1.unlock(); // no-warning + sm2.lock(); // no-warning + sm2.unlock(); // no-warning +} + +void sm_ok6(void) { + sm1.lock(); // no-warning + sm2.lock(); // no-warning + sm2.unlock(); // no-warning + sm1.unlock(); // no-warning +} + +void sm_ok7(void) { + if (sm1.try_lock()) // no-warning + sm1.unlock(); // no-warning +} + +void sm_ok8(void) { + sm1.unlock(); // no-warning + if (sm1.try_lock()) // no-warning + sm1.unlock(); // no-warning +} + +void sm_ok9(void) { + if (!sm1.try_lock()) // no-warning + sm1.lock(); // no-warning + sm1.unlock(); // no-warning +} + +void sm_ok10() { + std::lock_guard gl(sm1); // no-warning +} + +// shared_mutex bad + +void sm_bad1() { + sm1.lock(); // no-warning + sm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void sm_bad2() { + sm1.lock(); // no-warning + sm1.unlock(); // no-warning + sm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void sm_bad3() { + sm1.lock(); // no-warning + sm2.lock(); // no-warning + sm1.unlock(); // expected-warning {{This was not the most recently acquired lock. Possible lock order reversal}} + sm2.unlock(); // no-warning +} + +void sm_bad5() { + while (true) + sm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void sm_bad6() { + while (true) + sm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void sm_bad7() { + if (sm1.try_lock()) // no-warning + sm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +// shared_mutex ok (shared semantics) + +void sms_ok1() { + sm1.lock_shared(); // no-warning +} + +void sms_ok2() { + sm1.unlock_shared(); // no-warning +} + +void sms_ok3() { + sm1.lock_shared(); // no-warning + sm1.unlock_shared(); // no-warning +} + +void sms_ok4() { + sm1.lock_shared(); // no-warning + sm1.unlock_shared(); // no-warning + sm1.lock_shared(); // no-warning + sm1.unlock_shared(); // no-warning +} + +void sms_ok5() { + sm1.lock_shared(); // no-warning + sm1.unlock_shared(); // no-warning + sm2.lock_shared(); // no-warning + sm2.unlock_shared(); // no-warning +} + +void sms_ok6(void) { + sm1.lock_shared(); // no-warning + sm2.lock_shared(); // no-warning + sm2.unlock_shared(); // no-warning + sm1.unlock_shared(); // no-warning +} + +void sms_ok7(void) { + if (sm1.try_lock_shared()) // no-warning + sm1.unlock_shared(); // no-warning +} + +void sms_ok8(void) { + sm1.unlock_shared(); // no-warning + if (sm1.try_lock_shared()) // no-warning + sm1.unlock_shared(); // no-warning +} + +void sms_ok9(void) { + if (!sm1.try_lock_shared()) // no-warning + sm1.lock_shared(); // no-warning + sm1.unlock_shared(); // no-warning +} + +void sms_ok10() { + std::shared_lock gl(sm1); // no-warning +} + +// shared_mutex bad (shared semantics) + +void sms_bad1() { + sm1.lock_shared(); // no-warning + sm1.lock_shared(); // expected-warning{{This lock has already been acquired}} +} + +void sms_bad2() { + sm1.lock(); // no-warning + sm1.lock_shared(); // expected-warning{{This lock has already been acquired}} +} + +void sms_bad3() { + sm1.lock_shared(); // no-warning + sm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void sms_bad4() { + sm1.lock_shared(); // no-warning + sm1.unlock_shared(); // no-warning + sm1.unlock_shared(); // expected-warning {{This lock has already been unlocked}} +} + +void sms_bad5() { + sm1.unlock_shared(); // no-warning + sm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void sms_bad6() { + sm1.unlock(); // no-warning + sm1.unlock_shared(); // expected-warning {{This lock has already been unlocked}} +} + +void sms_bad7() { + sm1.lock_shared(); // no-warning + sm2.lock_shared(); // no-warning + sm1.unlock_shared(); // expected-warning {{This was not the most recently acquired lock. Possible lock order reversal}} + sm2.unlock_shared(); // no-warning +} + +void sms_bad8() { + while (true) + sm1.unlock_shared(); // expected-warning {{This lock has already been unlocked}} +} + +void sms_bad9() { + while (true) + sm1.lock_shared(); // expected-warning{{This lock has already been acquired}} +} + +void sms_bad10() { + if (sm1.try_lock_shared()) // no-warning + sm1.lock_shared(); // expected-warning{{This lock has already been acquired}} +} + +void sms_bad11() { + sm1.lock_shared(); // no-warning + sm1.unlock(); // expected-warning{{This lock has been locked as shared, but exclusive unlock used}} +} + +void sms_bad12() { + sm1.lock(); // no-warning + sm1.unlock_shared(); // expected-warning{{This lock has been locked exclusively, but shared unlock used}} +} + +void sms_bad13() { + if (sm1.try_lock_shared()) // no-warning + sm1.unlock(); // expected-warning{{This lock has been locked as shared, but exclusive unlock used}} +} + +void sms_bad14() { + if (sm1.try_lock()) // no-warning + sm1.unlock_shared(); // expected-warning{{This lock has been locked exclusively, but shared unlock used}} +} + +std::shared_timed_mutex stm1; +std::shared_timed_mutex stm2; + +// shared_timed_mutex ok + +void stm_ok1() { + stm1.lock(); // no-warning +} + +void stm_ok2() { + stm1.unlock(); // no-warning +} + +void stm_ok3() { + stm1.lock(); // no-warning + stm1.unlock(); // no-warning +} + +void stm_ok4() { + stm1.lock(); // no-warning + stm1.unlock(); // no-warning + stm1.lock(); // no-warning + stm1.unlock(); // no-warning +} + +void stm_ok5() { + stm1.lock(); // no-warning + stm1.unlock(); // no-warning + stm2.lock(); // no-warning + stm2.unlock(); // no-warning +} + +void stm_ok6(void) { + stm1.lock(); // no-warning + stm2.lock(); // no-warning + stm2.unlock(); // no-warning + stm1.unlock(); // no-warning +} + +void stm_ok7(void) { + if (stm1.try_lock()) // no-warning + stm1.unlock(); // no-warning +} + +void stm_ok8(void) { + stm1.unlock(); // no-warning + if (stm1.try_lock()) // no-warning + stm1.unlock(); // no-warning +} + +void stm_ok9(void) { + if (!stm1.try_lock()) // no-warning + stm1.lock(); // no-warning + stm1.unlock(); // no-warning +} + +void stm_ok10() { + std::lock_guard gl(stm1); // no-warning +} + +// shared_timed_mutex bad + +void stm_bad1() { + stm1.lock(); // no-warning + stm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void stm_bad2() { + stm1.lock(); // no-warning + stm1.unlock(); // no-warning + stm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void stm_bad3() { + stm1.lock(); // no-warning + stm2.lock(); // no-warning + stm1.unlock(); // expected-warning {{This was not the most recently acquired lock. Possible lock order reversal}} + stm2.unlock(); // no-warning +} + +void stm_bad5() { + while (true) + stm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void stm_bad6() { + while (true) + stm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void stm_bad7() { + if (stm1.try_lock()) // no-warning + stm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +// shared_timed_mutex ok (shared semantics) + +void stms_ok1() { + stm1.lock_shared(); // no-warning +} + +void stms_ok2() { + stm1.unlock_shared(); // no-warning +} + +void stms_ok3() { + stm1.lock_shared(); // no-warning + stm1.unlock_shared(); // no-warning +} + +void stms_ok4() { + stm1.lock_shared(); // no-warning + stm1.unlock_shared(); // no-warning + stm1.lock_shared(); // no-warning + stm1.unlock_shared(); // no-warning +} + +void stms_ok5() { + stm1.lock_shared(); // no-warning + stm1.unlock_shared(); // no-warning + stm2.lock_shared(); // no-warning + stm2.unlock_shared(); // no-warning +} + +void stms_ok6(void) { + stm1.lock_shared(); // no-warning + stm2.lock_shared(); // no-warning + stm2.unlock_shared(); // no-warning + stm1.unlock_shared(); // no-warning +} + +void stms_ok7(void) { + if (stm1.try_lock_shared()) // no-warning + stm1.unlock_shared(); // no-warning +} + +void stms_ok8(void) { + stm1.unlock_shared(); // no-warning + if (stm1.try_lock_shared()) // no-warning + stm1.unlock_shared(); // no-warning +} + +void stms_ok9(void) { + if (!stm1.try_lock_shared()) // no-warning + stm1.lock_shared(); // no-warning + stm1.unlock_shared(); // no-warning +} + +void stms_ok10() { + std::shared_lock gl(stm1); // no-warning +} + +// shared_timed_mutex bad (shared semantics) + +void stms_bad1() { + stm1.lock_shared(); // no-warning + stm1.lock_shared(); // expected-warning{{This lock has already been acquired}} +} + +void stms_bad2() { + stm1.lock(); // no-warning + stm1.lock_shared(); // expected-warning{{This lock has already been acquired}} +} + +void stms_bad3() { + stm1.lock_shared(); // no-warning + stm1.lock(); // expected-warning{{This lock has already been acquired}} +} + +void stms_bad4() { + stm1.lock_shared(); // no-warning + stm1.unlock_shared(); // no-warning + stm1.unlock_shared(); // expected-warning {{This lock has already been unlocked}} +} + +void stms_bad5() { + stm1.unlock_shared(); // no-warning + stm1.unlock(); // expected-warning {{This lock has already been unlocked}} +} + +void stms_bad6() { + stm1.unlock(); // no-warning + stm1.unlock_shared(); // expected-warning {{This lock has already been unlocked}} +} + +void stms_bad7() { + stm1.lock_shared(); // no-warning + stm2.lock_shared(); // no-warning + stm1.unlock_shared(); // expected-warning {{This was not the most recently acquired lock. Possible lock order reversal}} + stm2.unlock_shared(); // no-warning +} + +void stms_bad8() { + while (true) + stm1.unlock_shared(); // expected-warning {{This lock has already been unlocked}} +} + +void stms_bad9() { + while (true) + stm1.lock_shared(); // expected-warning{{This lock has already been acquired}} +} + +void stms_bad10() { + if (stm1.try_lock_shared()) // no-warning + stm1.lock_shared(); // expected-warning{{This lock has already been acquired}} +} + +void stms_bad11() { + stm1.lock_shared(); // no-warning + stm1.unlock(); // expected-warning{{This lock has been locked as shared, but exclusive unlock used}} +} + +void stms_bad12() { + stm1.lock(); // no-warning + stm1.unlock_shared(); // expected-warning{{This lock has been locked exclusively, but shared unlock used}} +} + +void stms_bad13() { + if (stm1.try_lock_shared()) // no-warning + stm1.unlock(); // expected-warning{{This lock has been locked as shared, but exclusive unlock used}} +} + +void stms_bad14() { + if (stm1.try_lock()) // no-warning + stm1.unlock_shared(); // expected-warning{{This lock has been locked exclusively, but shared unlock used}} +}