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,10 @@ 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,118 @@ +//=== 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/AST/StmtVisitor.h" +#include "clang/AST/ParentMap.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/APFloat.h" + +using namespace clang; +using namespace ento; + +namespace { + +class ThreadPrimitivesChecker + : public Checker { +public: + ThreadPrimitivesChecker(); + + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; +private: + mutable std::unique_ptr BT; + CallDescription LockFunc, UnlockFunc; + + std::pair FindMutexLockOrUnlock(const CallEvent &Call, + CheckerContext &C) const; + void reportBug(CheckerContext &C, const char Msg[]) const; +}; + +} // namespace + +REGISTER_SET_WITH_PROGRAMSTATE(LockedMutexes, SVal) + +ThreadPrimitivesChecker::ThreadPrimitivesChecker() + : LockFunc({"std","mutex","lock"}, 0, 0), UnlockFunc({"std","mutex","unlock"}, 0, 0) {} + + +std::pair ThreadPrimitivesChecker::FindMutexLockOrUnlock( + const CallEvent &Call, CheckerContext &C) const { + + if (const auto *MCall = dyn_cast(&Call)) { + const bool IsLockFunc = MCall->isCalled(LockFunc); + const bool IsUnlockFunc = IsLockFunc ? false : MCall->isCalled(UnlockFunc); + return {IsLockFunc, IsUnlockFunc}; + } + return {false, false}; +} + +void ThreadPrimitivesChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + // Find mutex::lock or mutex::unlock functions. + bool IsLockFunc; + bool IsUnlockFunc; + std::tie(IsLockFunc, IsUnlockFunc) = FindMutexLockOrUnlock(Call, C); + + if (!IsLockFunc && !IsUnlockFunc) + return; + + // We are sure about cast here, because mutex::lock/unlock met before. + const auto *MCall = dyn_cast(&Call); + assert(MCall); + SVal SymVal = MCall->getCXXThisVal(); + const bool LockFound = C.getState()->contains(SymVal); + + if (LockFound) { + if (IsLockFunc) { + reportBug(C, "Call mutex::lock more than once."); + } else if (IsUnlockFunc) { + ProgramStateRef State = C.getState()->remove(SymVal); + C.addTransition(State); + } + } else { + if (IsLockFunc) { + ProgramStateRef State = C.getState()->add(SymVal); + C.addTransition(State); + } else if (IsUnlockFunc) { + reportBug(C, "unlock without lock"); + } + } +} + +void ThreadPrimitivesChecker::reportBug(CheckerContext &C, + const char Msg[]) const { + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(); + if (!ErrNode) + return; + + if (!BT) + BT = std::make_unique(this, "STL thread primitives misuse", + "std::mutex misuse"); + + // Generate a report for this bug. + auto R = std::make_unique(*BT, Msg, ErrNode); + C.emitReport(std::move(R)); +} + +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,48 @@ +// 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(); + m.unlock(); +} + +void incorrect_lock_unlock(std::mutex m1, std::mutex m2) { + m1.lock(); + m2.unlock(); // expected-warning{{TRUE}} +} + +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{{TRUE}} +} + +void twice_lock(std::mutex m) { + m.lock(); + m.lock(); // expected-warning{{TRUE}} +} + +void twice_lock2(std::mutex m) { + while (true) + m.lock(); // expected-warning{{TRUE}} +} \ No newline at end of file