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{};

42 const MemRegion *LockReg{};

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 {

60 CallDescription LockFn;

61 CallDescription UnlockFn;

62

63public:

64 CallDescriptionBasedMatcher(CallDescription &&LockFn,

65 CallDescription &&UnlockFn)

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

67 [[nodiscard]] bool matches(const CallEvent &Call, bool IsLock) const {

68 if (IsLock) {

69 return LockFn.matches(Call);

70 }

71 return UnlockFn.matches(Call);

72 }

73};

74

75class FirstArgMutexDescriptor : public CallDescriptionBasedMatcher {

76public:

77 FirstArgMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)

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

79

80 [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call, bool) const {

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

82 }

83};

84

85class MemberMutexDescriptor : public CallDescriptionBasedMatcher {

86public:

87 MemberMutexDescriptor(CallDescription &&LockFn, CallDescription &&UnlockFn)

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

89

90 [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call, bool) const {

92 }

93};

94

95class RAIIMutexDescriptor {

96 mutable const IdentifierInfo *Guard{};

97 mutable bool IdentifierInfoInitialized{};

98 mutable llvm::SmallString<32> GuardName{};

99

100 void initIdentifierInfo(const CallEvent &Call) const {

101 if (!IdentifierInfoInitialized) {

102

103

104

105

106

107 const auto &ASTCtx = Call.getASTContext();

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;

116 const IdentifierInfo *II =

118 if (II != Guard)

119 return false;

120

121

122

123 if constexpr (std::is_same_v<T, CXXConstructorCall>) {

124 if (GuardName == "unique_lock" && C->getNumArgs() >= 2) {

125 const Expr *SecondArg = C->getArgExpr(1);

128 RD && RD->getName() == "defer_lock_t" && RD->isInStdNamespace()) {

129 return false;

130 }

131 }

132 }

133

134 return true;

135 }

136

137public:

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

139 [[nodiscard]] bool matches(const CallEvent &Call, bool IsLock) const {

140 initIdentifierInfo(Call);

141 if (IsLock) {

142 return matchesImpl(Call);

143 }

144 return matchesImpl(Call);

145 }

146 [[nodiscard]] const MemRegion *getRegion(const CallEvent &Call,

147 bool IsLock) const {

148 const MemRegion *LockRegion = nullptr;

149 if (IsLock) {

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

151 LockRegion = Object->getAsRegion();

152 }

153 } else {

155 }

156 return LockRegion;

157 }

158};

159

160using MutexDescriptor =

161 std::variant<FirstArgMutexDescriptor, MemberMutexDescriptor,

162 RAIIMutexDescriptor>;

163

165private:

166 const CallDescription OpenFunction{CDM::CLibrary, {"open"}, 2};

168 const int NonBlockMacroVal;

169 bool Satisfied = false;

170

171public:

172 SuppressNonBlockingStreams(SymbolRef StreamSym, int NonBlockMacroVal)

173 : StreamSym(StreamSym), NonBlockMacroVal(NonBlockMacroVal) {}

174

175 static void *getTag() {

176 static bool Tag;

177 return &Tag;

178 }

179

180 void Profile(llvm::FoldingSetNodeID &ID) const override {

181 ID.AddPointer(getTag());

182 }

183

185 BugReporterContext &BRC,

186 PathSensitiveBugReport &BR) override {

187 if (Satisfied)

188 return nullptr;

189

190 std::optional Point = N->getLocationAs();

191 if (!Point)

192 return nullptr;

193

194 const auto *CE = Point->getStmtAs();

195 if (!CE || !OpenFunction.matchesAsWritten(*CE))

196 return nullptr;

197

199 return nullptr;

200

201 Satisfied = true;

202

203

205 if (!FlagVal)

206 return nullptr;

207

208 if ((*FlagVal & NonBlockMacroVal) != 0)

210

211 return nullptr;

212 }

213};

214

215class BlockInCriticalSectionChecker : public Checkercheck::PostCall {

216private:

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

218

219

220

221

222

223

224

225 MemberMutexDescriptor(

226 {CDM::CXXMethod,

227 {"std", "lock"},

228 0},

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

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

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

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

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

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

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

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

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

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

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

240 RAIIMutexDescriptor("lock_guard"),

241 RAIIMutexDescriptor("unique_lock")};

242

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

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

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

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

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

248

249 const BugType BlockInCritSectionBugType{

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

251

252 using O_NONBLOCKValueTy = std::optional;

253 mutable std::optional<O_NONBLOCKValueTy> O_NONBLOCKValue;

254

255 void reportBlockInCritSection(const CallEvent &call, CheckerContext &C) const;

256

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

258 CheckerContext &C) const;

259

260 [[nodiscard]] std::optional

261 checkDescriptorMatch(const CallEvent &Call, CheckerContext &C,

262 bool IsLock) const;

263

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

265 CheckerContext &C) const;

266

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

268 CheckerContext &C) const;

269

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

271 CheckerContext &C) const;

272

273public:

274

275

276

277 void checkPostCall(const CallEvent &Call, CheckerContext &C) const;

278};

279

280}

281

283

284

285

286

287

288template <>

289struct std::iterator_traits<llvm::ImmutableList::iterator> {

295};

296

297std::optional

298BlockInCriticalSectionChecker::checkDescriptorMatch(const CallEvent &Call,

300 bool IsLock) const {

301 const auto Descriptor =

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

303 return std::visit(

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

305 return DescriptorImpl.matches(Call, IsLock);

306 },

307 Descriptor);

308 });

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

310 return *Descriptor;

311 return std::nullopt;

312}

313

315 while (Reg) {

316 const auto *BaseClassRegion = dyn_cast(Reg);

318 break;

319 Reg = BaseClassRegion->getSuperRegion();

320 }

321 return Reg;

322}

323

325 const MutexDescriptor &Descriptor,

326 bool IsLock) {

327 return std::visit(

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

330 },

331 Descriptor);

332}

333

334void BlockInCriticalSectionChecker::handleLock(

335 const MutexDescriptor &LockDescriptor, const CallEvent &Call,

336 CheckerContext &C) const {

337 const MemRegion *MutexRegion =

338 getRegion(Call, LockDescriptor, true);

339 if (!MutexRegion)

340 return;

341

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

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

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

346}

347

348void BlockInCriticalSectionChecker::handleUnlock(

349 const MutexDescriptor &UnlockDescriptor, const CallEvent &Call,

350 CheckerContext &C) const {

351 const MemRegion *MutexRegion =

352 getRegion(Call, UnlockDescriptor, false);

353 if (!MutexRegion)

354 return;

355

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

358 const auto MostRecentLock =

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

360 return Marker.LockReg == MutexRegion;

361 });

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

363 return;

364

365

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

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

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

369 ++It) {

370 if (It != MostRecentLock)

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

372 }

373

374 State = State->set(NewList);

375 C.addTransition(State);

376}

377

378bool BlockInCriticalSectionChecker::isBlockingInCritSection(

379 const CallEvent &Call, CheckerContext &C) const {

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

382}

383

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

385 CheckerContext &C) const {

386 if (isBlockingInCritSection(Call, C)) {

387 reportBlockInCritSection(Call, C);

388 } else if (std::optional LockDesc =

389 checkDescriptorMatch(Call, C, true)) {

390 handleLock(*LockDesc, Call, C);

391 } else if (std::optional UnlockDesc =

392 checkDescriptorMatch(Call, C, false)) {

393 handleUnlock(*UnlockDesc, Call, C);

394 }

395}

396

397void BlockInCriticalSectionChecker::reportBlockInCritSection(

398 const CallEvent &Call, CheckerContext &C) const {

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

400 if (!ErrNode)

401 return;

402

403 std::string msg;

404 llvm::raw_string_ostream os(msg);

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

406 << "' inside of critical section";

407 auto R = std::make_unique(BlockInCritSectionBugType,

408 os.str(), ErrNode);

409

410

411

412

413 StringRef FuncName = Call.getCalleeIdentifier()->getName();

414 if (FuncName == "read" || FuncName == "recv") {

415 SVal SV = Call.getArgSVal(0);

416 SValBuilder &SVB = C.getSValBuilder();

418 ConditionTruthVal CTV =

419 state->areEqual(SV, SVB.makeIntVal(-1, C.getASTContext().IntTy));

421 return;

422

424 if (!O_NONBLOCKValue)

426 "O_NONBLOCK", C.getBugReporter().getPreprocessor());

427 if (*O_NONBLOCKValue)

428 R->addVisitor(SR, **O_NONBLOCKValue);

429 }

430 }

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

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

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

434}

435

436const NoteTag *

437BlockInCriticalSectionChecker::createCritSectionNote(CritSectionMarker M,

438 CheckerContext &C) const {

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

440 return C.getNoteTag([M, BT](PathSensitiveBugReport &BR,

441 llvm::raw_ostream &OS) {

443 return;

444

445

446 const auto CritSectionBegins =

448 llvm::SmallVector<CritSectionMarker, 4> LocksForMutex;

449 llvm::copy_if(

450 CritSectionBegins, std::back_inserter(LocksForMutex),

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

452 if (LocksForMutex.empty())

453 return;

454

455

456

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

458

459

460

461 const auto Position =

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

463 return Marker.LockExpr == M.LockExpr;

464 });

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

466 return;

467

468

469

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

471 OS << "Entering critical section here";

472 return;

473 }

474

475 const auto IndexOfLock =

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

477

478 const auto OrdinalOfLock = IndexOfLock + 1;

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

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

481 });

482}

483

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

486}

487

488bool ento::shouldRegisterBlockInCriticalSectionChecker(

489 const CheckerManager &mgr) {

490 return true;

491}

static const MemRegion * skipStdBaseClassRegion(const MemRegion *Reg)

Definition BlockInCriticalSectionChecker.cpp:314

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

Definition BlockInCriticalSectionChecker.cpp:324

#define REGISTER_LIST_WITH_PROGRAMSTATE(Name, Elem)

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

StringRef getName() const

Get the name of identifier for this declaration as a StringRef.

QualType getNonReferenceType() const

If Type is a reference type (e.g., const int&), returns the type that the reference refers to ("const...

RecordDecl * getAsRecordDecl() const

Retrieves the RecordDecl this type refers to.

const BugType & getBugType() const

BugReporterVisitors are used to add custom diagnostics along a path.

bool contains(const CallEvent &Call) const

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

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

Register a single-part checker (derived from Checker): construct its singleton instance,...

Simple checker classes that implement one frontend (i.e.

bool isConstrainedTrue() const

Return true if the constraint is perfectly constrained to 'true'.

const ProgramStateRef & getState() const

SVal getSVal(const Stmt *S) const

Get the value of an arbitrary expression at this node.

std::optional< T > getLocationAs() const &

MemRegion - The root abstract class for all memory regions.

const ExplodedNode * getErrorNode() const

void markInvalid(const void *Tag, const void *Data)

Marks the current report as invalid, meaning that it is probably a false positive and should not be r...

nonloc::ConcreteInt makeIntVal(const IntegerLiteral *integer)

SymbolRef getAsSymbol(bool IncludeBaseRegions=false) const

If this SVal wraps a symbol return that SymbolRef.

const llvm::APSInt * getAsInteger() const

If this SVal is loc::ConcreteInt or nonloc::ConcreteInt, return a pointer to APSInt which is held in ...

bool isWithinStdNamespace(const Decl *D)

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

IntrusiveRefCntPtr< const ProgramState > ProgramStateRef

const SymExpr * SymbolRef

@ OS

Indicates that the tracking object is a descendant of a referenced-counted OSObject,...

std::optional< int > tryExpandAsInteger(StringRef Macro, const Preprocessor &PP)

Try to parse the value of a defined preprocessor macro.

std::shared_ptr< PathDiagnosticPiece > PathDiagnosticPieceRef

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

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

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

const FunctionProtoType * T

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

U cast(CodeGen::Address addr)

@ Other

Other implicit parameter.

Diagnostic wrappers for TextAPI types for error reporting.

CritSectionMarker & reference

Definition BlockInCriticalSectionChecker.cpp:293

CritSectionMarker * pointer

Definition BlockInCriticalSectionChecker.cpp:294

std::ptrdiff_t difference_type

Definition BlockInCriticalSectionChecker.cpp:292

CritSectionMarker value_type

Definition BlockInCriticalSectionChecker.cpp:291

std::forward_iterator_tag iterator_category

Definition BlockInCriticalSectionChecker.cpp:290