Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -743,6 +743,10 @@ "of the same container are expected">, Dependencies<[IteratorModeling]>, Documentation; + +def ThreadPrimitivesChecker : Checker<"ThreadPrimitives">, + HelpText<"Check STD synchronization primitives">, + Documentation; def SmartPtrChecker: Checker<"SmartPtr">, HelpText<"Find the dereference of null SmrtPtr">, Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h @@ -193,7 +193,7 @@ const ProgramPointTag *Tag = nullptr) { if (!State) State = getState(); - addTransition(State, generateSink(State, getPredecessor())); + addTransition(State, generateSink(State, getPredecessor(), Tag)); } /// Generate a transition to a node that will be used to report Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/SVals.h @@ -133,6 +133,8 @@ return !(*this == R); } + bool operator<(const SVal &R) const { return Data < R.Data; } + bool isUnknown() const { return getRawKind() == UnknownValKind; } Index: clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -107,6 +107,7 @@ Taint.cpp TaintTesterChecker.cpp TestAfterDivZeroChecker.cpp + ThreadPrimitivesChecker.cpp TraversalChecker.cpp TrustNonnullChecker.cpp UndefBranchChecker.cpp Index: clang/lib/StaticAnalyzer/Checkers/ThreadPrimitivesChecker.cpp =================================================================== --- /dev/null +++ clang/lib/StaticAnalyzer/Checkers/ThreadPrimitivesChecker.cpp @@ -0,0 +1,113 @@ +//=== ConversionChecker.cpp -------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This checker finds STL thread primitives misuse. +// - std::mutex::unlock without std::mutex::lock +// - std::mutex::lock twice +// +//===----------------------------------------------------------------------===// +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include + +using namespace clang; +using namespace ento; + +namespace { + +enum FuncIdKind : uint8_t { + Undefined = 0, + Mutex_Lock, + Mutex_Unlock, +}; + +static const std::pair FuncMapping[]{ + {Mutex_Lock, {{"std", "mutex", "lock"}, 0, 0}}, + {Mutex_Unlock, {{"std", "mutex", "unlock"}, 0, 0}}, +}; + +class ThreadPrimitivesChecker : public Checker { +public: + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + +private: + BugType BT{this, "STL thread primitives misuse", "std::mutex misuse"}; + + FuncIdKind FindMutexLockOrUnlock(const CallEvent &Call, + CheckerContext &C) const; + void reportBug(CheckerContext &C, const char Msg[]) const; +}; + +} // namespace + +REGISTER_SET_WITH_PROGRAMSTATE(LockedMutexes, SVal) + +FuncIdKind +ThreadPrimitivesChecker::FindMutexLockOrUnlock(const CallEvent &Call, + CheckerContext &C) const { + if (const auto *MCall = dyn_cast(&Call)) + for (auto &P : FuncMapping) + if (MCall->isCalled(P.second)) + return P.first; + return Undefined; +} + +void ThreadPrimitivesChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + // Find mutex::lock or mutex::unlock functions. + FuncIdKind FuncId = FindMutexLockOrUnlock(Call, C); + + if (FuncId == Undefined) + return; + + // We are sure about cast here, because mutex::lock/unlock met before. + const auto *MCall = cast(&Call); + SVal SymVal = MCall->getCXXThisVal(); + SymVal.dump(); + const bool LockFound = C.getState()->contains(SymVal); + + switch (FuncId) { + case Mutex_Lock: + if (LockFound) { + reportBug(C, "Call mutex::lock more than once."); + } else { + ProgramStateRef State = C.getState()->add(SymVal); + C.addTransition(State); + } + break; + case Mutex_Unlock: + if (LockFound) { + ProgramStateRef State = C.getState()->remove(SymVal); + C.addTransition(State); + } else { + reportBug(C, "unlock without lock"); + } + break; + } +} + +void ThreadPrimitivesChecker::reportBug(CheckerContext &C, + const char Msg[]) const { + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); + if (!ErrNode) + return; + + // Generate a report for this bug. + C.emitReport(std::make_unique(BT, Msg, ErrNode)); +} + +void ento::registerThreadPrimitivesChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterThreadPrimitivesChecker(const CheckerManager &mgr) { + return true; +} Index: clang/test/Analysis/Checkers/ThreadPrimitivesChecker.cpp =================================================================== --- /dev/null +++ clang/test/Analysis/Checkers/ThreadPrimitivesChecker.cpp @@ -0,0 +1,64 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.cplusplus.ThreadPrimitives -verify %s + +namespace std { +struct mutex { + void lock(); + void unlock(); +}; +template +struct guard_lock { + T &t; + guard_lock(T &m) : t(m) { + t.lock(); + } + ~guard_lock() { + t.unlock(); + } +}; +} // namespace std + +void correct_lock_unlock(std::mutex m) { + m.lock(); + std::mutex *m2 = &m; + m2->unlock(); +} + +void correct_lock_unlock2(std::mutex m) { + m.lock(); + std::mutex &m2 = m; + m2.unlock(); +} + +void correct_lock_unlock3(std::mutex m) { + m.lock(); + m.unlock(); +} + +void correct_lock_unlock4(std::mutex m) { + std::guard_lock l(m); +} + +void incorrect_lock_unlock(std::mutex m1, std::mutex m2) { + m1.lock(); + m2.unlock(); // expected-warning{{unlock without lock}} +} + +void incorrect_lock_unlock2(std::mutex m, bool b) { + m.lock(); + if (b) + m.unlock(); +} + +void unlock_without_lock(std::mutex m) { + m.unlock(); // expected-warning{{unlock without lock}} +} + +void twice_lock(std::mutex m) { + m.lock(); + m.lock(); // expected-warning{{lock more than once}} +} + +void twice_lock2(std::mutex m) { + while (true) + m.lock(); // expected-warning{{lock more than once}} +}