1 //===-- StreamChecker.cpp -----------------------------------------*- C++ -*--// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file defines checkers that model and check stream handling functions. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 14 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 15 #include "clang/StaticAnalyzer/Core/Checker.h" 16 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 17 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" 18 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 19 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 20 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" 21 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 22 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 23 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" 24 #include <functional> 25 #include <optional> 26 27 using namespace clang; 28 using namespace ento; 29 using namespace std::placeholders; 30 31 //===----------------------------------------------------------------------===// 32 // Definition of state data structures. 33 //===----------------------------------------------------------------------===// 34 35 namespace { 36 37 struct FnDescription; 38 39 /// State of the stream error flags. 40 /// Sometimes it is not known to the checker what error flags are set. 41 /// This is indicated by setting more than one flag to true. 42 /// This is an optimization to avoid state splits. 43 /// A stream can either be in FEOF or FERROR but not both at the same time. 44 /// Multiple flags are set to handle the corresponding states together. 45 struct StreamErrorState { 46 /// The stream can be in state where none of the error flags set. 47 bool NoError = true; 48 /// The stream can be in state where the EOF indicator is set. 49 bool FEof = false; 50 /// The stream can be in state where the error indicator is set. 51 bool FError = false; 52 53 bool isNoError() const { return NoError && !FEof && !FError; } 54 bool isFEof() const { return !NoError && FEof && !FError; } 55 bool isFError() const { return !NoError && !FEof && FError; } 56 57 bool operator==(const StreamErrorState &ES) const { 58 return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError; 59 } 60 61 bool operator!=(const StreamErrorState &ES) const { return !(*this == ES); } 62 63 StreamErrorState operator|(const StreamErrorState &E) const { 64 return {NoError || E.NoError, FEof || E.FEof, FError || E.FError}; 65 } 66 67 StreamErrorState operator&(const StreamErrorState &E) const { 68 return {NoError && E.NoError, FEof && E.FEof, FError && E.FError}; 69 } 70 71 StreamErrorState operator~() const { return {!NoError, !FEof, !FError}; } 72 73 /// Returns if the StreamErrorState is a valid object. 74 operator bool() const { return NoError || FEof || FError; } 75 76 void Profile(llvm::FoldingSetNodeID &ID) const { 77 ID.AddBoolean(NoError); 78 ID.AddBoolean(FEof); 79 ID.AddBoolean(FError); 80 } 81 }; 82 83 const StreamErrorState ErrorNone{true, false, false}; 84 const StreamErrorState ErrorFEof{false, true, false}; 85 const StreamErrorState ErrorFError{false, false, true}; 86 87 /// Full state information about a stream pointer. 88 struct StreamState { 89 /// The last file operation called in the stream. 90 /// Can be nullptr. 91 const FnDescription *LastOperation; 92 93 /// State of a stream symbol. 94 enum KindTy { 95 Opened, /// Stream is opened. 96 Closed, /// Closed stream (an invalid stream pointer after it was closed). 97 OpenFailed /// The last open operation has failed. 98 } State; 99 100 /// State of the error flags. 101 /// Ignored in non-opened stream state but must be NoError. 102 StreamErrorState const ErrorState; 103 104 /// Indicate if the file has an "indeterminate file position indicator". 105 /// This can be set at a failing read or write or seek operation. 106 /// If it is set no more read or write is allowed. 107 /// This value is not dependent on the stream error flags: 108 /// The error flag may be cleared with `clearerr` but the file position 109 /// remains still indeterminate. 110 /// This value applies to all error states in ErrorState except FEOF. 111 /// An EOF+indeterminate state is the same as EOF state. 112 bool const FilePositionIndeterminate = false; 113 114 StreamState(const FnDescription *L, KindTy S, const StreamErrorState &ES, 115 bool IsFilePositionIndeterminate) 116 : LastOperation(L), State(S), ErrorState(ES), 117 FilePositionIndeterminate(IsFilePositionIndeterminate) { 118 assert((!ES.isFEof() || !IsFilePositionIndeterminate) && 119 "FilePositionIndeterminate should be false in FEof case."); 120 assert((State == Opened || ErrorState.isNoError()) && 121 "ErrorState should be None in non-opened stream state."); 122 } 123 124 bool isOpened() const { return State == Opened; } 125 bool isClosed() const { return State == Closed; } 126 bool isOpenFailed() const { return State == OpenFailed; } 127 128 bool operator==(const StreamState &X) const { 129 // In not opened state error state should always NoError, so comparison 130 // here is no problem. 131 return LastOperation == X.LastOperation && State == X.State && 132 ErrorState == X.ErrorState && 133 FilePositionIndeterminate == X.FilePositionIndeterminate; 134 } 135 136 static StreamState getOpened(const FnDescription *L, 137 const StreamErrorState &ES = ErrorNone, 138 bool IsFilePositionIndeterminate = false) { 139 return StreamState{L, Opened, ES, IsFilePositionIndeterminate}; 140 } 141 static StreamState getClosed(const FnDescription *L) { 142 return StreamState{L, Closed, {}, false}; 143 } 144 static StreamState getOpenFailed(const FnDescription *L) { 145 return StreamState{L, OpenFailed, {}, false}; 146 } 147 148 void Profile(llvm::FoldingSetNodeID &ID) const { 149 ID.AddPointer(LastOperation); 150 ID.AddInteger(State); 151 ErrorState.Profile(ID); 152 ID.AddBoolean(FilePositionIndeterminate); 153 } 154 }; 155 156 } // namespace 157 158 //===----------------------------------------------------------------------===// 159 // StreamChecker class and utility functions. 160 //===----------------------------------------------------------------------===// 161 162 namespace { 163 164 class StreamChecker; 165 using FnCheck = std::function<void(const StreamChecker *, const FnDescription *, 166 const CallEvent &, CheckerContext &)>; 167 168 using ArgNoTy = unsigned int; 169 static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max(); 170 171 struct FnDescription { 172 FnCheck PreFn; 173 FnCheck EvalFn; 174 ArgNoTy StreamArgNo; 175 }; 176 177 /// Get the value of the stream argument out of the passed call event. 178 /// The call should contain a function that is described by Desc. 179 SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) { 180 assert(Desc && Desc->StreamArgNo != ArgNone && 181 "Try to get a non-existing stream argument."); 182 return Call.getArgSVal(Desc->StreamArgNo); 183 } 184 185 /// Create a conjured symbol return value for a call expression. 186 DefinedSVal makeRetVal(CheckerContext &C, const CallExpr *CE) { 187 assert(CE && "Expecting a call expression."); 188 189 const LocationContext *LCtx = C.getLocationContext(); 190 return C.getSValBuilder() 191 .conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) 192 .castAs<DefinedSVal>(); 193 } 194 195 ProgramStateRef bindAndAssumeTrue(ProgramStateRef State, CheckerContext &C, 196 const CallExpr *CE) { 197 DefinedSVal RetVal = makeRetVal(C, CE); 198 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 199 State = State->assume(RetVal, true); 200 assert(State && "Assumption on new value should not fail."); 201 return State; 202 } 203 204 ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State, 205 CheckerContext &C, const CallExpr *CE) { 206 State = State->BindExpr(CE, C.getLocationContext(), 207 C.getSValBuilder().makeIntVal(Value, CE->getType())); 208 return State; 209 } 210 211 class StreamChecker : public Checker<check::PreCall, eval::Call, 212 check::DeadSymbols, check::PointerEscape> { 213 BugType BT_FileNull{this, "NULL stream pointer", "Stream handling error"}; 214 BugType BT_UseAfterClose{this, "Closed stream", "Stream handling error"}; 215 BugType BT_UseAfterOpenFailed{this, "Invalid stream", 216 "Stream handling error"}; 217 BugType BT_IndeterminatePosition{this, "Invalid stream state", 218 "Stream handling error"}; 219 BugType BT_IllegalWhence{this, "Illegal whence argument", 220 "Stream handling error"}; 221 BugType BT_StreamEof{this, "Stream already in EOF", "Stream handling error"}; 222 BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error", 223 /*SuppressOnSink =*/true}; 224 225 public: 226 void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 227 bool evalCall(const CallEvent &Call, CheckerContext &C) const; 228 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 229 ProgramStateRef checkPointerEscape(ProgramStateRef State, 230 const InvalidatedSymbols &Escaped, 231 const CallEvent *Call, 232 PointerEscapeKind Kind) const; 233 234 /// If true, evaluate special testing stream functions. 235 bool TestMode = false; 236 237 const BugType *getBT_StreamEof() const { return &BT_StreamEof; } 238 239 private: 240 CallDescriptionMap<FnDescription> FnDescriptions = { 241 {{{"fopen"}}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, 242 {{{"freopen"}, 3}, 243 {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}}, 244 {{{"tmpfile"}}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, 245 {{{"fclose"}, 1}, 246 {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}}, 247 {{{"fread"}, 4}, 248 {&StreamChecker::preFread, 249 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}}, 250 {{{"fwrite"}, 4}, 251 {&StreamChecker::preFwrite, 252 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}}, 253 {{{"fseek"}, 3}, 254 {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}}, 255 {{{"ftell"}, 1}, 256 {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}}, 257 {{{"rewind"}, 1}, 258 {&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}}, 259 {{{"fgetpos"}, 2}, 260 {&StreamChecker::preDefault, &StreamChecker::evalFgetpos, 0}}, 261 {{{"fsetpos"}, 2}, 262 {&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}}, 263 {{{"clearerr"}, 1}, 264 {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}}, 265 {{{"feof"}, 1}, 266 {&StreamChecker::preDefault, 267 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof), 268 0}}, 269 {{{"ferror"}, 1}, 270 {&StreamChecker::preDefault, 271 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError), 272 0}}, 273 {{{"fileno"}, 1}, {&StreamChecker::preDefault, nullptr, 0}}, 274 }; 275 276 CallDescriptionMap<FnDescription> FnTestDescriptions = { 277 {{{"StreamTesterChecker_make_feof_stream"}, 1}, 278 {nullptr, 279 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof), 280 0}}, 281 {{{"StreamTesterChecker_make_ferror_stream"}, 1}, 282 {nullptr, 283 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, 284 ErrorFError), 285 0}}, 286 }; 287 288 mutable std::optional<int> EofVal; 289 290 void evalFopen(const FnDescription *Desc, const CallEvent &Call, 291 CheckerContext &C) const; 292 293 void preFreopen(const FnDescription *Desc, const CallEvent &Call, 294 CheckerContext &C) const; 295 void evalFreopen(const FnDescription *Desc, const CallEvent &Call, 296 CheckerContext &C) const; 297 298 void evalFclose(const FnDescription *Desc, const CallEvent &Call, 299 CheckerContext &C) const; 300 301 void preFread(const FnDescription *Desc, const CallEvent &Call, 302 CheckerContext &C) const; 303 304 void preFwrite(const FnDescription *Desc, const CallEvent &Call, 305 CheckerContext &C) const; 306 307 void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call, 308 CheckerContext &C, bool IsFread) const; 309 310 void preFseek(const FnDescription *Desc, const CallEvent &Call, 311 CheckerContext &C) const; 312 void evalFseek(const FnDescription *Desc, const CallEvent &Call, 313 CheckerContext &C) const; 314 315 void evalFgetpos(const FnDescription *Desc, const CallEvent &Call, 316 CheckerContext &C) const; 317 318 void evalFsetpos(const FnDescription *Desc, const CallEvent &Call, 319 CheckerContext &C) const; 320 321 void evalFtell(const FnDescription *Desc, const CallEvent &Call, 322 CheckerContext &C) const; 323 324 void evalRewind(const FnDescription *Desc, const CallEvent &Call, 325 CheckerContext &C) const; 326 327 void preDefault(const FnDescription *Desc, const CallEvent &Call, 328 CheckerContext &C) const; 329 330 void evalClearerr(const FnDescription *Desc, const CallEvent &Call, 331 CheckerContext &C) const; 332 333 void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call, 334 CheckerContext &C, 335 const StreamErrorState &ErrorKind) const; 336 337 void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call, 338 CheckerContext &C, 339 const StreamErrorState &ErrorKind) const; 340 341 /// Check that the stream (in StreamVal) is not NULL. 342 /// If it can only be NULL a fatal error is emitted and nullptr returned. 343 /// Otherwise the return value is a new state where the stream is constrained 344 /// to be non-null. 345 ProgramStateRef ensureStreamNonNull(SVal StreamVal, const Expr *StreamE, 346 CheckerContext &C, 347 ProgramStateRef State) const; 348 349 /// Check that the stream is the opened state. 350 /// If the stream is known to be not opened an error is generated 351 /// and nullptr returned, otherwise the original state is returned. 352 ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C, 353 ProgramStateRef State) const; 354 355 /// Check that the stream has not an invalid ("indeterminate") file position, 356 /// generate warning for it. 357 /// (EOF is not an invalid position.) 358 /// The returned state can be nullptr if a fatal error was generated. 359 /// It can return non-null state if the stream has not an invalid position or 360 /// there is execution path with non-invalid position. 361 ProgramStateRef 362 ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C, 363 ProgramStateRef State) const; 364 365 /// Check the legality of the 'whence' argument of 'fseek'. 366 /// Generate error and return nullptr if it is found to be illegal. 367 /// Otherwise returns the state. 368 /// (State is not changed here because the "whence" value is already known.) 369 ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 370 ProgramStateRef State) const; 371 372 /// Generate warning about stream in EOF state. 373 /// There will be always a state transition into the passed State, 374 /// by the new non-fatal error node or (if failed) a normal transition, 375 /// to ensure uniform handling. 376 void reportFEofWarning(SymbolRef StreamSym, CheckerContext &C, 377 ProgramStateRef State) const; 378 379 /// Emit resource leak warnings for the given symbols. 380 /// Createn a non-fatal error node for these, and returns it (if any warnings 381 /// were generated). Return value is non-null. 382 ExplodedNode *reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms, 383 CheckerContext &C, ExplodedNode *Pred) const; 384 385 /// Find the description data of the function called by a call event. 386 /// Returns nullptr if no function is recognized. 387 const FnDescription *lookupFn(const CallEvent &Call) const { 388 // Recognize "global C functions" with only integral or pointer arguments 389 // (and matching name) as stream functions. 390 if (!Call.isGlobalCFunction()) 391 return nullptr; 392 for (auto *P : Call.parameters()) { 393 QualType T = P->getType(); 394 if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) 395 return nullptr; 396 } 397 398 return FnDescriptions.lookup(Call); 399 } 400 401 /// Generate a message for BugReporterVisitor if the stored symbol is 402 /// marked as interesting by the actual bug report. 403 // FIXME: Use lambda instead. 404 struct NoteFn { 405 const BugType *BT_ResourceLeak; 406 SymbolRef StreamSym; 407 std::string Message; 408 409 std::string operator()(PathSensitiveBugReport &BR) const { 410 if (BR.isInteresting(StreamSym) && &BR.getBugType() == BT_ResourceLeak) 411 return Message; 412 413 return ""; 414 } 415 }; 416 417 const NoteTag *constructNoteTag(CheckerContext &C, SymbolRef StreamSym, 418 const std::string &Message) const { 419 return C.getNoteTag(NoteFn{&BT_ResourceLeak, StreamSym, Message}); 420 } 421 422 const NoteTag *constructSetEofNoteTag(CheckerContext &C, 423 SymbolRef StreamSym) const { 424 return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) { 425 if (!BR.isInteresting(StreamSym) || 426 &BR.getBugType() != this->getBT_StreamEof()) 427 return ""; 428 429 BR.markNotInteresting(StreamSym); 430 431 return "Assuming stream reaches end-of-file here"; 432 }); 433 } 434 435 void initEof(CheckerContext &C) const { 436 if (EofVal) 437 return; 438 439 if (const std::optional<int> OptInt = 440 tryExpandAsInteger("EOF", C.getPreprocessor())) 441 EofVal = *OptInt; 442 else 443 EofVal = -1; 444 } 445 446 /// Searches for the ExplodedNode where the file descriptor was acquired for 447 /// StreamSym. 448 static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N, 449 SymbolRef StreamSym, 450 CheckerContext &C); 451 }; 452 453 } // end anonymous namespace 454 455 // This map holds the state of a stream. 456 // The stream is identified with a SymbolRef that is created when a stream 457 // opening function is modeled by the checker. 458 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 459 460 inline void assertStreamStateOpened(const StreamState *SS) { 461 assert(SS->isOpened() && "Stream is expected to be opened"); 462 } 463 464 const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, 465 SymbolRef StreamSym, 466 CheckerContext &C) { 467 ProgramStateRef State = N->getState(); 468 // When bug type is resource leak, exploded node N may not have state info 469 // for leaked file descriptor, but predecessor should have it. 470 if (!State->get<StreamMap>(StreamSym)) 471 N = N->getFirstPred(); 472 473 const ExplodedNode *Pred = N; 474 while (N) { 475 State = N->getState(); 476 if (!State->get<StreamMap>(StreamSym)) 477 return Pred; 478 Pred = N; 479 N = N->getFirstPred(); 480 } 481 482 return nullptr; 483 } 484 485 //===----------------------------------------------------------------------===// 486 // Methods of StreamChecker. 487 //===----------------------------------------------------------------------===// 488 489 void StreamChecker::checkPreCall(const CallEvent &Call, 490 CheckerContext &C) const { 491 initEof(C); 492 493 const FnDescription *Desc = lookupFn(Call); 494 if (!Desc || !Desc->PreFn) 495 return; 496 497 Desc->PreFn(this, Desc, Call, C); 498 } 499 500 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { 501 const FnDescription *Desc = lookupFn(Call); 502 if (!Desc && TestMode) 503 Desc = FnTestDescriptions.lookup(Call); 504 if (!Desc || !Desc->EvalFn) 505 return false; 506 507 Desc->EvalFn(this, Desc, Call, C); 508 509 return C.isDifferent(); 510 } 511 512 void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, 513 CheckerContext &C) const { 514 ProgramStateRef State = C.getState(); 515 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 516 if (!CE) 517 return; 518 519 DefinedSVal RetVal = makeRetVal(C, CE); 520 SymbolRef RetSym = RetVal.getAsSymbol(); 521 assert(RetSym && "RetVal must be a symbol here."); 522 523 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 524 525 // Bifurcate the state into two: one with a valid FILE* pointer, the other 526 // with a NULL. 527 ProgramStateRef StateNotNull, StateNull; 528 std::tie(StateNotNull, StateNull) = 529 C.getConstraintManager().assumeDual(State, RetVal); 530 531 StateNotNull = 532 StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc)); 533 StateNull = 534 StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc)); 535 536 C.addTransition(StateNotNull, 537 constructNoteTag(C, RetSym, "Stream opened here")); 538 C.addTransition(StateNull); 539 } 540 541 void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, 542 CheckerContext &C) const { 543 // Do not allow NULL as passed stream pointer but allow a closed stream. 544 ProgramStateRef State = C.getState(); 545 State = ensureStreamNonNull(getStreamArg(Desc, Call), 546 Call.getArgExpr(Desc->StreamArgNo), C, State); 547 if (!State) 548 return; 549 550 C.addTransition(State); 551 } 552 553 void StreamChecker::evalFreopen(const FnDescription *Desc, 554 const CallEvent &Call, 555 CheckerContext &C) const { 556 ProgramStateRef State = C.getState(); 557 558 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 559 if (!CE) 560 return; 561 562 std::optional<DefinedSVal> StreamVal = 563 getStreamArg(Desc, Call).getAs<DefinedSVal>(); 564 if (!StreamVal) 565 return; 566 567 SymbolRef StreamSym = StreamVal->getAsSymbol(); 568 // Do not care about concrete values for stream ("(FILE *)0x12345"?). 569 // FIXME: Can be stdin, stdout, stderr such values? 570 if (!StreamSym) 571 return; 572 573 // Do not handle untracked stream. It is probably escaped. 574 if (!State->get<StreamMap>(StreamSym)) 575 return; 576 577 // Generate state for non-failed case. 578 // Return value is the passed stream pointer. 579 // According to the documentations, the stream is closed first 580 // but any close error is ignored. The state changes to (or remains) opened. 581 ProgramStateRef StateRetNotNull = 582 State->BindExpr(CE, C.getLocationContext(), *StreamVal); 583 // Generate state for NULL return value. 584 // Stream switches to OpenFailed state. 585 ProgramStateRef StateRetNull = 586 State->BindExpr(CE, C.getLocationContext(), 587 C.getSValBuilder().makeNullWithType(CE->getType())); 588 589 StateRetNotNull = 590 StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); 591 StateRetNull = 592 StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc)); 593 594 C.addTransition(StateRetNotNull, 595 constructNoteTag(C, StreamSym, "Stream reopened here")); 596 C.addTransition(StateRetNull); 597 } 598 599 void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, 600 CheckerContext &C) const { 601 ProgramStateRef State = C.getState(); 602 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); 603 if (!Sym) 604 return; 605 606 const StreamState *SS = State->get<StreamMap>(Sym); 607 if (!SS) 608 return; 609 610 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 611 if (!CE) 612 return; 613 614 assertStreamStateOpened(SS); 615 616 // Close the File Descriptor. 617 // Regardless if the close fails or not, stream becomes "closed" 618 // and can not be used any more. 619 State = State->set<StreamMap>(Sym, StreamState::getClosed(Desc)); 620 621 // Return 0 on success, EOF on failure. 622 SValBuilder &SVB = C.getSValBuilder(); 623 ProgramStateRef StateSuccess = State->BindExpr( 624 CE, C.getLocationContext(), SVB.makeIntVal(0, C.getASTContext().IntTy)); 625 ProgramStateRef StateFailure = 626 State->BindExpr(CE, C.getLocationContext(), 627 SVB.makeIntVal(*EofVal, C.getASTContext().IntTy)); 628 629 C.addTransition(StateSuccess); 630 C.addTransition(StateFailure); 631 } 632 633 void StreamChecker::preFread(const FnDescription *Desc, const CallEvent &Call, 634 CheckerContext &C) const { 635 ProgramStateRef State = C.getState(); 636 SVal StreamVal = getStreamArg(Desc, Call); 637 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C, 638 State); 639 if (!State) 640 return; 641 State = ensureStreamOpened(StreamVal, C, State); 642 if (!State) 643 return; 644 State = ensureNoFilePositionIndeterminate(StreamVal, C, State); 645 if (!State) 646 return; 647 648 SymbolRef Sym = StreamVal.getAsSymbol(); 649 if (Sym && State->get<StreamMap>(Sym)) { 650 const StreamState *SS = State->get<StreamMap>(Sym); 651 if (SS->ErrorState & ErrorFEof) 652 reportFEofWarning(Sym, C, State); 653 } else { 654 C.addTransition(State); 655 } 656 } 657 658 void StreamChecker::preFwrite(const FnDescription *Desc, const CallEvent &Call, 659 CheckerContext &C) const { 660 ProgramStateRef State = C.getState(); 661 SVal StreamVal = getStreamArg(Desc, Call); 662 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C, 663 State); 664 if (!State) 665 return; 666 State = ensureStreamOpened(StreamVal, C, State); 667 if (!State) 668 return; 669 State = ensureNoFilePositionIndeterminate(StreamVal, C, State); 670 if (!State) 671 return; 672 673 C.addTransition(State); 674 } 675 676 void StreamChecker::evalFreadFwrite(const FnDescription *Desc, 677 const CallEvent &Call, CheckerContext &C, 678 bool IsFread) const { 679 ProgramStateRef State = C.getState(); 680 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 681 if (!StreamSym) 682 return; 683 684 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 685 if (!CE) 686 return; 687 688 std::optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>(); 689 if (!SizeVal) 690 return; 691 std::optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>(); 692 if (!NMembVal) 693 return; 694 695 const StreamState *OldSS = State->get<StreamMap>(StreamSym); 696 if (!OldSS) 697 return; 698 699 assertStreamStateOpened(OldSS); 700 701 // C'99 standard, §7.19.8.1.3, the return value of fread: 702 // The fread function returns the number of elements successfully read, which 703 // may be less than nmemb if a read error or end-of-file is encountered. If 704 // size or nmemb is zero, fread returns zero and the contents of the array and 705 // the state of the stream remain unchanged. 706 707 if (State->isNull(*SizeVal).isConstrainedTrue() || 708 State->isNull(*NMembVal).isConstrainedTrue()) { 709 // This is the "size or nmemb is zero" case. 710 // Just return 0, do nothing more (not clear the error flags). 711 State = bindInt(0, State, C, CE); 712 C.addTransition(State); 713 return; 714 } 715 716 // Generate a transition for the success state. 717 // If we know the state to be FEOF at fread, do not add a success state. 718 if (!IsFread || (OldSS->ErrorState != ErrorFEof)) { 719 ProgramStateRef StateNotFailed = 720 State->BindExpr(CE, C.getLocationContext(), *NMembVal); 721 StateNotFailed = 722 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); 723 C.addTransition(StateNotFailed); 724 } 725 726 // Add transition for the failed state. 727 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); 728 ProgramStateRef StateFailed = 729 State->BindExpr(CE, C.getLocationContext(), RetVal); 730 auto Cond = 731 C.getSValBuilder() 732 .evalBinOpNN(State, BO_LT, RetVal, *NMembVal, C.getASTContext().IntTy) 733 .getAs<DefinedOrUnknownSVal>(); 734 if (!Cond) 735 return; 736 StateFailed = StateFailed->assume(*Cond, true); 737 if (!StateFailed) 738 return; 739 740 StreamErrorState NewES; 741 if (IsFread) 742 NewES = 743 (OldSS->ErrorState == ErrorFEof) ? ErrorFEof : ErrorFEof | ErrorFError; 744 else 745 NewES = ErrorFError; 746 // If a (non-EOF) error occurs, the resulting value of the file position 747 // indicator for the stream is indeterminate. 748 StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof()); 749 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS); 750 if (IsFread && OldSS->ErrorState != ErrorFEof) 751 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym)); 752 else 753 C.addTransition(StateFailed); 754 } 755 756 void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, 757 CheckerContext &C) const { 758 ProgramStateRef State = C.getState(); 759 SVal StreamVal = getStreamArg(Desc, Call); 760 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C, 761 State); 762 if (!State) 763 return; 764 State = ensureStreamOpened(StreamVal, C, State); 765 if (!State) 766 return; 767 State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State); 768 if (!State) 769 return; 770 771 C.addTransition(State); 772 } 773 774 void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call, 775 CheckerContext &C) const { 776 ProgramStateRef State = C.getState(); 777 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 778 if (!StreamSym) 779 return; 780 781 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 782 if (!CE) 783 return; 784 785 // Ignore the call if the stream is not tracked. 786 if (!State->get<StreamMap>(StreamSym)) 787 return; 788 789 DefinedSVal RetVal = makeRetVal(C, CE); 790 791 // Make expression result. 792 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 793 794 // Bifurcate the state into failed and non-failed. 795 // Return zero on success, nonzero on error. 796 ProgramStateRef StateNotFailed, StateFailed; 797 std::tie(StateFailed, StateNotFailed) = 798 C.getConstraintManager().assumeDual(State, RetVal); 799 800 // Reset the state to opened with no error. 801 StateNotFailed = 802 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); 803 // We get error. 804 // It is possible that fseek fails but sets none of the error flags. 805 // If fseek failed, assume that the file position becomes indeterminate in any 806 // case. 807 StateFailed = StateFailed->set<StreamMap>( 808 StreamSym, 809 StreamState::getOpened(Desc, ErrorNone | ErrorFEof | ErrorFError, true)); 810 811 C.addTransition(StateNotFailed); 812 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym)); 813 } 814 815 void StreamChecker::evalFgetpos(const FnDescription *Desc, 816 const CallEvent &Call, 817 CheckerContext &C) const { 818 ProgramStateRef State = C.getState(); 819 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); 820 if (!Sym) 821 return; 822 823 // Do not evaluate if stream is not found. 824 if (!State->get<StreamMap>(Sym)) 825 return; 826 827 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 828 if (!CE) 829 return; 830 831 DefinedSVal RetVal = makeRetVal(C, CE); 832 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 833 ProgramStateRef StateNotFailed, StateFailed; 834 std::tie(StateFailed, StateNotFailed) = 835 C.getConstraintManager().assumeDual(State, RetVal); 836 837 // This function does not affect the stream state. 838 // Still we add success and failure state with the appropriate return value. 839 // StdLibraryFunctionsChecker can change these states (set the 'errno' state). 840 C.addTransition(StateNotFailed); 841 C.addTransition(StateFailed); 842 } 843 844 void StreamChecker::evalFsetpos(const FnDescription *Desc, 845 const CallEvent &Call, 846 CheckerContext &C) const { 847 ProgramStateRef State = C.getState(); 848 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 849 if (!StreamSym) 850 return; 851 852 const StreamState *SS = State->get<StreamMap>(StreamSym); 853 if (!SS) 854 return; 855 856 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 857 if (!CE) 858 return; 859 860 assertStreamStateOpened(SS); 861 862 DefinedSVal RetVal = makeRetVal(C, CE); 863 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 864 ProgramStateRef StateNotFailed, StateFailed; 865 std::tie(StateFailed, StateNotFailed) = 866 C.getConstraintManager().assumeDual(State, RetVal); 867 868 StateNotFailed = StateNotFailed->set<StreamMap>( 869 StreamSym, StreamState::getOpened(Desc, ErrorNone, false)); 870 871 // At failure ferror could be set. 872 // The standards do not tell what happens with the file position at failure. 873 // But we can assume that it is dangerous to make a next I/O operation after 874 // the position was not set correctly (similar to 'fseek'). 875 StateFailed = StateFailed->set<StreamMap>( 876 StreamSym, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true)); 877 878 C.addTransition(StateNotFailed); 879 C.addTransition(StateFailed); 880 } 881 882 void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call, 883 CheckerContext &C) const { 884 ProgramStateRef State = C.getState(); 885 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); 886 if (!Sym) 887 return; 888 889 if (!State->get<StreamMap>(Sym)) 890 return; 891 892 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 893 if (!CE) 894 return; 895 896 SValBuilder &SVB = C.getSValBuilder(); 897 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); 898 ProgramStateRef StateNotFailed = 899 State->BindExpr(CE, C.getLocationContext(), RetVal); 900 auto Cond = SVB.evalBinOp(State, BO_GE, RetVal, 901 SVB.makeZeroVal(C.getASTContext().LongTy), 902 SVB.getConditionType()) 903 .getAs<DefinedOrUnknownSVal>(); 904 if (!Cond) 905 return; 906 StateNotFailed = StateNotFailed->assume(*Cond, true); 907 if (!StateNotFailed) 908 return; 909 910 ProgramStateRef StateFailed = State->BindExpr( 911 CE, C.getLocationContext(), SVB.makeIntVal(-1, C.getASTContext().LongTy)); 912 913 C.addTransition(StateNotFailed); 914 C.addTransition(StateFailed); 915 } 916 917 void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call, 918 CheckerContext &C) const { 919 ProgramStateRef State = C.getState(); 920 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 921 if (!StreamSym) 922 return; 923 924 const StreamState *SS = State->get<StreamMap>(StreamSym); 925 if (!SS) 926 return; 927 928 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 929 if (!CE) 930 return; 931 932 assertStreamStateOpened(SS); 933 934 State = State->set<StreamMap>(StreamSym, 935 StreamState::getOpened(Desc, ErrorNone, false)); 936 937 C.addTransition(State); 938 } 939 940 void StreamChecker::evalClearerr(const FnDescription *Desc, 941 const CallEvent &Call, 942 CheckerContext &C) const { 943 ProgramStateRef State = C.getState(); 944 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 945 if (!StreamSym) 946 return; 947 948 const StreamState *SS = State->get<StreamMap>(StreamSym); 949 if (!SS) 950 return; 951 952 assertStreamStateOpened(SS); 953 954 // FilePositionIndeterminate is not cleared. 955 State = State->set<StreamMap>( 956 StreamSym, 957 StreamState::getOpened(Desc, ErrorNone, SS->FilePositionIndeterminate)); 958 C.addTransition(State); 959 } 960 961 void StreamChecker::evalFeofFerror(const FnDescription *Desc, 962 const CallEvent &Call, CheckerContext &C, 963 const StreamErrorState &ErrorKind) const { 964 ProgramStateRef State = C.getState(); 965 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 966 if (!StreamSym) 967 return; 968 969 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 970 if (!CE) 971 return; 972 973 const StreamState *SS = State->get<StreamMap>(StreamSym); 974 if (!SS) 975 return; 976 977 assertStreamStateOpened(SS); 978 979 if (SS->ErrorState & ErrorKind) { 980 // Execution path with error of ErrorKind. 981 // Function returns true. 982 // From now on it is the only one error state. 983 ProgramStateRef TrueState = bindAndAssumeTrue(State, C, CE); 984 C.addTransition(TrueState->set<StreamMap>( 985 StreamSym, StreamState::getOpened(Desc, ErrorKind, 986 SS->FilePositionIndeterminate && 987 !ErrorKind.isFEof()))); 988 } 989 if (StreamErrorState NewES = SS->ErrorState & (~ErrorKind)) { 990 // Execution path(s) with ErrorKind not set. 991 // Function returns false. 992 // New error state is everything before minus ErrorKind. 993 ProgramStateRef FalseState = bindInt(0, State, C, CE); 994 C.addTransition(FalseState->set<StreamMap>( 995 StreamSym, 996 StreamState::getOpened( 997 Desc, NewES, SS->FilePositionIndeterminate && !NewES.isFEof()))); 998 } 999 } 1000 1001 void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call, 1002 CheckerContext &C) const { 1003 ProgramStateRef State = C.getState(); 1004 SVal StreamVal = getStreamArg(Desc, Call); 1005 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C, 1006 State); 1007 if (!State) 1008 return; 1009 State = ensureStreamOpened(StreamVal, C, State); 1010 if (!State) 1011 return; 1012 1013 C.addTransition(State); 1014 } 1015 1016 void StreamChecker::evalSetFeofFerror(const FnDescription *Desc, 1017 const CallEvent &Call, CheckerContext &C, 1018 const StreamErrorState &ErrorKind) const { 1019 ProgramStateRef State = C.getState(); 1020 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 1021 assert(StreamSym && "Operation not permitted on non-symbolic stream value."); 1022 const StreamState *SS = State->get<StreamMap>(StreamSym); 1023 assert(SS && "Stream should be tracked by the checker."); 1024 State = State->set<StreamMap>( 1025 StreamSym, StreamState::getOpened(SS->LastOperation, ErrorKind)); 1026 C.addTransition(State); 1027 } 1028 1029 ProgramStateRef 1030 StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE, 1031 CheckerContext &C, 1032 ProgramStateRef State) const { 1033 auto Stream = StreamVal.getAs<DefinedSVal>(); 1034 if (!Stream) 1035 return State; 1036 1037 ConstraintManager &CM = C.getConstraintManager(); 1038 1039 ProgramStateRef StateNotNull, StateNull; 1040 std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *Stream); 1041 1042 if (!StateNotNull && StateNull) { 1043 if (ExplodedNode *N = C.generateErrorNode(StateNull)) { 1044 auto R = std::make_unique<PathSensitiveBugReport>( 1045 BT_FileNull, "Stream pointer might be NULL.", N); 1046 if (StreamE) 1047 bugreporter::trackExpressionValue(N, StreamE, *R); 1048 C.emitReport(std::move(R)); 1049 } 1050 return nullptr; 1051 } 1052 1053 return StateNotNull; 1054 } 1055 1056 ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal, 1057 CheckerContext &C, 1058 ProgramStateRef State) const { 1059 SymbolRef Sym = StreamVal.getAsSymbol(); 1060 if (!Sym) 1061 return State; 1062 1063 const StreamState *SS = State->get<StreamMap>(Sym); 1064 if (!SS) 1065 return State; 1066 1067 if (SS->isClosed()) { 1068 // Using a stream pointer after 'fclose' causes undefined behavior 1069 // according to cppreference.com . 1070 ExplodedNode *N = C.generateErrorNode(); 1071 if (N) { 1072 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1073 BT_UseAfterClose, 1074 "Stream might be already closed. Causes undefined behaviour.", N)); 1075 return nullptr; 1076 } 1077 1078 return State; 1079 } 1080 1081 if (SS->isOpenFailed()) { 1082 // Using a stream that has failed to open is likely to cause problems. 1083 // This should usually not occur because stream pointer is NULL. 1084 // But freopen can cause a state when stream pointer remains non-null but 1085 // failed to open. 1086 ExplodedNode *N = C.generateErrorNode(); 1087 if (N) { 1088 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1089 BT_UseAfterOpenFailed, 1090 "Stream might be invalid after " 1091 "(re-)opening it has failed. " 1092 "Can cause undefined behaviour.", 1093 N)); 1094 return nullptr; 1095 } 1096 return State; 1097 } 1098 1099 return State; 1100 } 1101 1102 ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate( 1103 SVal StreamVal, CheckerContext &C, ProgramStateRef State) const { 1104 static const char *BugMessage = 1105 "File position of the stream might be 'indeterminate' " 1106 "after a failed operation. " 1107 "Can cause undefined behavior."; 1108 1109 SymbolRef Sym = StreamVal.getAsSymbol(); 1110 if (!Sym) 1111 return State; 1112 1113 const StreamState *SS = State->get<StreamMap>(Sym); 1114 if (!SS) 1115 return State; 1116 1117 assert(SS->isOpened() && "First ensure that stream is opened."); 1118 1119 if (SS->FilePositionIndeterminate) { 1120 if (SS->ErrorState & ErrorFEof) { 1121 // The error is unknown but may be FEOF. 1122 // Continue analysis with the FEOF error state. 1123 // Report warning because the other possible error states. 1124 ExplodedNode *N = C.generateNonFatalErrorNode(State); 1125 if (!N) 1126 return nullptr; 1127 1128 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1129 BT_IndeterminatePosition, BugMessage, N)); 1130 return State->set<StreamMap>( 1131 Sym, StreamState::getOpened(SS->LastOperation, ErrorFEof, false)); 1132 } 1133 1134 // Known or unknown error state without FEOF possible. 1135 // Stop analysis, report error. 1136 ExplodedNode *N = C.generateErrorNode(State); 1137 if (N) 1138 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1139 BT_IndeterminatePosition, BugMessage, N)); 1140 1141 return nullptr; 1142 } 1143 1144 return State; 1145 } 1146 1147 ProgramStateRef 1148 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 1149 ProgramStateRef State) const { 1150 std::optional<nonloc::ConcreteInt> CI = 1151 WhenceVal.getAs<nonloc::ConcreteInt>(); 1152 if (!CI) 1153 return State; 1154 1155 int64_t X = CI->getValue().getSExtValue(); 1156 if (X >= 0 && X <= 2) 1157 return State; 1158 1159 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 1160 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1161 BT_IllegalWhence, 1162 "The whence argument to fseek() should be " 1163 "SEEK_SET, SEEK_END, or SEEK_CUR.", 1164 N)); 1165 return nullptr; 1166 } 1167 1168 return State; 1169 } 1170 1171 void StreamChecker::reportFEofWarning(SymbolRef StreamSym, CheckerContext &C, 1172 ProgramStateRef State) const { 1173 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 1174 auto R = std::make_unique<PathSensitiveBugReport>( 1175 BT_StreamEof, 1176 "Read function called when stream is in EOF state. " 1177 "Function has no effect.", 1178 N); 1179 R->markInteresting(StreamSym); 1180 C.emitReport(std::move(R)); 1181 return; 1182 } 1183 C.addTransition(State); 1184 } 1185 1186 ExplodedNode * 1187 StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms, 1188 CheckerContext &C, ExplodedNode *Pred) const { 1189 ExplodedNode *Err = C.generateNonFatalErrorNode(C.getState(), Pred); 1190 if (!Err) 1191 return Pred; 1192 1193 for (SymbolRef LeakSym : LeakedSyms) { 1194 // Resource leaks can result in multiple warning that describe the same kind 1195 // of programming error: 1196 // void f() { 1197 // FILE *F = fopen("a.txt"); 1198 // if (rand()) // state split 1199 // return; // warning 1200 // } // warning 1201 // While this isn't necessarily true (leaking the same stream could result 1202 // from a different kinds of errors), the reduction in redundant reports 1203 // makes this a worthwhile heuristic. 1204 // FIXME: Add a checker option to turn this uniqueing feature off. 1205 const ExplodedNode *StreamOpenNode = getAcquisitionSite(Err, LeakSym, C); 1206 assert(StreamOpenNode && "Could not find place of stream opening."); 1207 PathDiagnosticLocation LocUsedForUniqueing = 1208 PathDiagnosticLocation::createBegin( 1209 StreamOpenNode->getStmtForDiagnostics(), C.getSourceManager(), 1210 StreamOpenNode->getLocationContext()); 1211 1212 std::unique_ptr<PathSensitiveBugReport> R = 1213 std::make_unique<PathSensitiveBugReport>( 1214 BT_ResourceLeak, 1215 "Opened stream never closed. Potential resource leak.", Err, 1216 LocUsedForUniqueing, 1217 StreamOpenNode->getLocationContext()->getDecl()); 1218 R->markInteresting(LeakSym); 1219 C.emitReport(std::move(R)); 1220 } 1221 1222 return Err; 1223 } 1224 1225 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 1226 CheckerContext &C) const { 1227 ProgramStateRef State = C.getState(); 1228 1229 llvm::SmallVector<SymbolRef, 2> LeakedSyms; 1230 1231 const StreamMapTy &Map = State->get<StreamMap>(); 1232 for (const auto &I : Map) { 1233 SymbolRef Sym = I.first; 1234 const StreamState &SS = I.second; 1235 if (!SymReaper.isDead(Sym)) 1236 continue; 1237 if (SS.isOpened()) 1238 LeakedSyms.push_back(Sym); 1239 State = State->remove<StreamMap>(Sym); 1240 } 1241 1242 ExplodedNode *N = C.getPredecessor(); 1243 if (!LeakedSyms.empty()) 1244 N = reportLeaks(LeakedSyms, C, N); 1245 1246 C.addTransition(State, N); 1247 } 1248 1249 ProgramStateRef StreamChecker::checkPointerEscape( 1250 ProgramStateRef State, const InvalidatedSymbols &Escaped, 1251 const CallEvent *Call, PointerEscapeKind Kind) const { 1252 // Check for file-handling system call that is not handled by the checker. 1253 // FIXME: The checker should be updated to handle all system calls that take 1254 // 'FILE*' argument. These are now ignored. 1255 if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader()) 1256 return State; 1257 1258 for (SymbolRef Sym : Escaped) { 1259 // The symbol escaped. 1260 // From now the stream can be manipulated in unknown way to the checker, 1261 // it is not possible to handle it any more. 1262 // Optimistically, assume that the corresponding file handle will be closed 1263 // somewhere else. 1264 // Remove symbol from state so the following stream calls on this symbol are 1265 // not handled by the checker. 1266 State = State->remove<StreamMap>(Sym); 1267 } 1268 return State; 1269 } 1270 1271 //===----------------------------------------------------------------------===// 1272 // Checker registration. 1273 //===----------------------------------------------------------------------===// 1274 1275 void ento::registerStreamChecker(CheckerManager &Mgr) { 1276 Mgr.registerChecker<StreamChecker>(); 1277 } 1278 1279 bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) { 1280 return true; 1281 } 1282 1283 void ento::registerStreamTesterChecker(CheckerManager &Mgr) { 1284 auto *Checker = Mgr.getChecker<StreamChecker>(); 1285 Checker->TestMode = true; 1286 } 1287 1288 bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) { 1289 return true; 1290 } 1291