clang: lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp Source File (original) (raw)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

27#include "llvm/ADT/STLExtras.h"

28#include "llvm/ADT/SmallString.h"

29#include "llvm/ADT/StringExtras.h"

30

31#include

32#include

33#include

34

35using namespace clang;

36using namespace ento;

37

38namespace {

39

40struct CritSectionMarker {

41 const Expr *LockExpr{};

43

44 void Profile(llvm::FoldingSetNodeID &ID) const {

45 ID.Add(LockExpr);

46 ID.Add(LockReg);

47 }

48

49 [[nodiscard]] constexpr bool

50 operator==(const CritSectionMarker &Other) const noexcept {

51 return LockExpr == Other.LockExpr && LockReg == Other.LockReg;

52 }

53 [[nodiscard]] constexpr bool

54 operator!=(const CritSectionMarker &Other) const noexcept {

55 return !(*this == Other);

56 }

57};

58

59class CallDescriptionBasedMatcher {

62

63public:

66 : LockFn(std::move(LockFn)), UnlockFn(std::move(UnlockFn)) {}

68 if (IsLock) {

70 }

72 }

73};

74

75class FirstArgMutexDescriptor : public CallDescriptionBasedMatcher {

76public:

78 : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}

79

81 return Call.getArgSVal(0).getAsRegion();

82 }

83};

84

85class MemberMutexDescriptor : public CallDescriptionBasedMatcher {

86public:

88 : CallDescriptionBasedMatcher(std::move(LockFn), std::move(UnlockFn)) {}

89

91 return cast(Call).getCXXThisVal().getAsRegion();

92 }

93};

94

95class RAIIMutexDescriptor {

97 mutable bool IdentifierInfoInitialized{};

99

100 void initIdentifierInfo(const CallEvent &Call) const {

101 if (!IdentifierInfoInitialized) {

102

103

104

105

106

107 const auto &ASTCtx = Call.getState()->getStateManager().getContext();

108 Guard = &ASTCtx.Idents.get(GuardName);

109 }

110 }

111

112 template bool matchesImpl(const CallEvent &Call) const {

113 const T *C = dyn_cast(&Call);

114 if (C)

115 return false;

117 cast(C->getDecl()->getParent())->getIdentifier();

118 return II == Guard;

119 }

120

121public:

122 RAIIMutexDescriptor(StringRef GuardName) : GuardName(GuardName) {}

124 initIdentifierInfo(Call);

125 if (IsLock) {

126 return matchesImpl(Call);

127 }

128 return matchesImpl(Call);

129 }

131 bool IsLock) const {

132 const MemRegion *LockRegion = nullptr;

133 if (IsLock) {

134 if (std::optional Object = Call.getReturnValueUnderConstruction()) {

135 LockRegion = Object->getAsRegion();

136 }

137 } else {

138 LockRegion = cast(Call).getCXXThisVal().getAsRegion();

139 }

140 return LockRegion;

141 }

142};

143

144using MutexDescriptor =

145 std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,

146 RAIIMutexDescriptor>;

147

148class BlockInCriticalSectionChecker : public Checkercheck::PostCall {

149private:

150 const std::array<MutexDescriptor, 8> MutexDescriptors{

151

152

153

154

155

156

157

158 MemberMutexDescriptor(

159 {CDM::CXXMethod,

160 {"std", "lock"},

161 0},

162 {CDM::CXXMethod, {"std", "unlock"}, 0}),

163 FirstArgMutexDescriptor({CDM::CLibrary, {"pthread_mutex_lock"}, 1},

164 {CDM::CLibrary, {"pthread_mutex_unlock"}, 1}),

165 FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_lock"}, 1},

166 {CDM::CLibrary, {"mtx_unlock"}, 1}),

167 FirstArgMutexDescriptor({CDM::CLibrary, {"pthread_mutex_trylock"}, 1},

168 {CDM::CLibrary, {"pthread_mutex_unlock"}, 1}),

169 FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_trylock"}, 1},

170 {CDM::CLibrary, {"mtx_unlock"}, 1}),

171 FirstArgMutexDescriptor({CDM::CLibrary, {"mtx_timedlock"}, 1},

172 {CDM::CLibrary, {"mtx_unlock"}, 1}),

173 RAIIMutexDescriptor("lock_guard"),

174 RAIIMutexDescriptor("unique_lock")};

175

176 const CallDescriptionSet BlockingFunctions{{CDM::CLibrary, {"sleep"}},

177 {CDM::CLibrary, {"getc"}},

178 {CDM::CLibrary, {"fgets"}},

179 {CDM::CLibrary, {"read"}},

180 {CDM::CLibrary, {"recv"}}};

181

182 const BugType BlockInCritSectionBugType{

183 this, "Call to blocking function in critical section", "Blocking Error"};

184

186

187 [[nodiscard]] const NoteTag *createCritSectionNote(CritSectionMarker M,

189

190 [[nodiscard]] std::optional

192 bool IsLock) const;

193

194 void handleLock(const MutexDescriptor &Mutex, const CallEvent &Call,

196

197 void handleUnlock(const MutexDescriptor &Mutex, const CallEvent &Call,

199

200 [[nodiscard]] bool isBlockingInCritSection(const CallEvent &Call,

202

203public:

204

205

206

208};

209

210}

211

213

214

215

216

217

218template <>

219struct std::iterator_traits<

220 typename llvm::ImmutableList::iterator> {

226};

227

228std::optional

229BlockInCriticalSectionChecker::checkDescriptorMatch(const CallEvent &Call,

231 bool IsLock) const {

232 const auto Descriptor =

233 llvm::find_if(MutexDescriptors, [&Call, IsLock](auto &&Descriptor) {

234 return std::visit(

235 [&Call, IsLock](auto &&DescriptorImpl) {

236 return DescriptorImpl.matches(Call, IsLock);

237 },

238 Descriptor);

239 });

240 if (Descriptor != MutexDescriptors.end())

241 return *Descriptor;

242 return std::nullopt;

243}

244

246 while (Reg) {

247 const auto *BaseClassRegion = dyn_cast(Reg);

249 break;

250 Reg = BaseClassRegion->getSuperRegion();

251 }

252 return Reg;

253}

254

256 const MutexDescriptor &Descriptor,

257 bool IsLock) {

258 return std::visit(

259 [&Call, IsLock](auto &Descr) -> const MemRegion * {

261 },

262 Descriptor);

263}

264

265void BlockInCriticalSectionChecker::handleLock(

266 const MutexDescriptor &LockDescriptor, const CallEvent &Call,

269 getRegion(Call, LockDescriptor, true);

270 if (!MutexRegion)

271 return;

272

273 const CritSectionMarker MarkToAdd{Call.getOriginExpr(), MutexRegion};

275 C.getState()->add(MarkToAdd);

276 C.addTransition(StateWithLockEvent, createCritSectionNote(MarkToAdd, C));

277}

278

279void BlockInCriticalSectionChecker::handleUnlock(

280 const MutexDescriptor &UnlockDescriptor, const CallEvent &Call,

283 getRegion(Call, UnlockDescriptor, false);

284 if (!MutexRegion)

285 return;

286

288 const auto ActiveSections = State->get();

289 const auto MostRecentLock =

290 llvm::find_if(ActiveSections, [MutexRegion](auto &&Marker) {

291 return Marker.LockReg == MutexRegion;

292 });

293 if (MostRecentLock == ActiveSections.end())

294 return;

295

296

297 auto &Factory = State->get_context();

298 llvm::ImmutableList NewList = Factory.getEmptyList();

299 for (auto It = ActiveSections.begin(), End = ActiveSections.end(); It != End;

300 ++It) {

301 if (It != MostRecentLock)

302 NewList = Factory.add(*It, NewList);

303 }

304

305 State = State->set(NewList);

306 C.addTransition(State);

307}

308

309bool BlockInCriticalSectionChecker::isBlockingInCritSection(

311 return BlockingFunctions.contains(Call) &&

312 C.getState()->get().isEmpty();

313}

314

315void BlockInCriticalSectionChecker::checkPostCall(const CallEvent &Call,

317 if (isBlockingInCritSection(Call, C)) {

318 reportBlockInCritSection(Call, C);

319 } else if (std::optional LockDesc =

320 checkDescriptorMatch(Call, C, true)) {

321 handleLock(*LockDesc, Call, C);

322 } else if (std::optional UnlockDesc =

323 checkDescriptorMatch(Call, C, false)) {

324 handleUnlock(*UnlockDesc, Call, C);

325 }

326}

327

328void BlockInCriticalSectionChecker::reportBlockInCritSection(

330 ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState());

331 if (!ErrNode)

332 return;

333

334 std::string msg;

335 llvm::raw_string_ostream os(msg);

336 os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName()

337 << "' inside of critical section";

338 auto R = std::make_unique(BlockInCritSectionBugType,

339 os.str(), ErrNode);

340 R->addRange(Call.getSourceRange());

341 R->markInteresting(Call.getReturnValue());

342 C.emitReport(std::move(R));

343}

344

346BlockInCriticalSectionChecker::createCritSectionNote(CritSectionMarker M,

348 const BugType *BT = &this->BlockInCritSectionBugType;

350 llvm::raw_ostream &OS) {

352 return;

353

354

355 const auto CritSectionBegins =

358 llvm::copy_if(

359 CritSectionBegins, std::back_inserter(LocksForMutex),

360 [M](const auto &Marker) { return Marker.LockReg == M.LockReg; });

361 if (LocksForMutex.empty())

362 return;

363

364

365

366 std::reverse(LocksForMutex.begin(), LocksForMutex.end());

367

368

369

370 const auto Position =

371 llvm::find_if(std::as_const(LocksForMutex), [M](const auto &Marker) {

372 return Marker.LockExpr == M.LockExpr;

373 });

374 if (Position == LocksForMutex.end())

375 return;

376

377

378

379 if (LocksForMutex.size() == 1) {

380 OS << "Entering critical section here";

381 return;

382 }

383

384 const auto IndexOfLock =

385 std::distance(std::as_const(LocksForMutex).begin(), Position);

386

387 const auto OrdinalOfLock = IndexOfLock + 1;

388 OS << "Entering critical section for the " << OrdinalOfLock

389 << llvm::getOrdinalSuffix(OrdinalOfLock) << " time here";

390 });

391}

392

393void ento::registerBlockInCriticalSectionChecker(CheckerManager &mgr) {

395}

396

397bool ento::shouldRegisterBlockInCriticalSectionChecker(

399 return true;

400}

static const MemRegion * skipStdBaseClassRegion(const MemRegion *Reg)

static const MemRegion * getRegion(const CallEvent &Call, const MutexDescriptor &Descriptor, bool IsLock)

#define REGISTER_LIST_WITH_PROGRAMSTATE(Name, Elem)

Declares an immutable list type NameTy, suitable for placement into the ProgramState.

This represents one expression.

One of these records is kept for each identifier that is lexed.

const BugType & getBugType() const

An immutable set of CallDescriptions.

A CallDescription is a pattern that can be used to match calls based on the qualified name and the ar...

bool matches(const CallEvent &Call) const

Returns true if the CallEvent is a call to a function that matches the CallDescription.

Represents an abstract call to a function or method along a particular path.

CHECKER * registerChecker(AT &&... Args)

Used to register checkers.

const ProgramStateRef & getState() const

MemRegion - The root abstract class for all memory regions.

The tag upon which the TagVisitor reacts.

const ExplodedNode * getErrorNode() const

bool isWithinStdNamespace(const Decl *D)

Returns true if declaration D is in std namespace or any nested namespace or class scope.

bool matches(const til::SExpr *E1, const til::SExpr *E2)

The JSON file list parser is used to communicate input to InstallAPI.

if(T->getSizeExpr()) TRY_TO(TraverseStmt(const_cast< Expr * >(T -> getSizeExpr())))

bool operator==(const CallGraphNode::CallRecord &LHS, const CallGraphNode::CallRecord &RHS)

bool operator!=(CanQual< T > x, CanQual< U > y)

const FunctionProtoType * T

@ Other

Other implicit parameter.

Diagnostic wrappers for TextAPI types for error reporting.

std::ptrdiff_t difference_type

CritSectionMarker & reference

CritSectionMarker * pointer

CritSectionMarker value_type

std::forward_iterator_tag iterator_category