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"}, 2}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, 242 {{{"freopen"}, 3}, 243 {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}}, 244 {{{"tmpfile"}, 0}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, 245 {{{"fclose"}, 1}, 246 {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}}, 247 {{{"fread"}, 4}, 248 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), 249 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}}, 250 {{{"fwrite"}, 4}, 251 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), 252 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}}, 253 {{{"fputc"}, 2}, 254 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), 255 &StreamChecker::evalFputc, 1}}, 256 {{{"fseek"}, 3}, 257 {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}}, 258 {{{"ftell"}, 1}, 259 {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}}, 260 {{{"rewind"}, 1}, 261 {&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}}, 262 {{{"fgetpos"}, 2}, 263 {&StreamChecker::preDefault, &StreamChecker::evalFgetpos, 0}}, 264 {{{"fsetpos"}, 2}, 265 {&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}}, 266 {{{"clearerr"}, 1}, 267 {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}}, 268 {{{"feof"}, 1}, 269 {&StreamChecker::preDefault, 270 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof), 271 0}}, 272 {{{"ferror"}, 1}, 273 {&StreamChecker::preDefault, 274 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError), 275 0}}, 276 {{{"fileno"}, 1}, {&StreamChecker::preDefault, nullptr, 0}}, 277 }; 278 279 CallDescriptionMap<FnDescription> FnTestDescriptions = { 280 {{{"StreamTesterChecker_make_feof_stream"}, 1}, 281 {nullptr, 282 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof), 283 0}}, 284 {{{"StreamTesterChecker_make_ferror_stream"}, 1}, 285 {nullptr, 286 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, 287 ErrorFError), 288 0}}, 289 }; 290 291 /// Expanded value of EOF, empty before initialization. 292 mutable std::optional<int> EofVal; 293 /// Expanded value of SEEK_SET, 0 if not found. 294 mutable int SeekSetVal = 0; 295 /// Expanded value of SEEK_CUR, 1 if not found. 296 mutable int SeekCurVal = 1; 297 /// Expanded value of SEEK_END, 2 if not found. 298 mutable int SeekEndVal = 2; 299 300 void evalFopen(const FnDescription *Desc, const CallEvent &Call, 301 CheckerContext &C) const; 302 303 void preFreopen(const FnDescription *Desc, const CallEvent &Call, 304 CheckerContext &C) const; 305 void evalFreopen(const FnDescription *Desc, const CallEvent &Call, 306 CheckerContext &C) const; 307 308 void evalFclose(const FnDescription *Desc, const CallEvent &Call, 309 CheckerContext &C) const; 310 311 void preReadWrite(const FnDescription *Desc, const CallEvent &Call, 312 CheckerContext &C, bool IsRead) const; 313 314 void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call, 315 CheckerContext &C, bool IsFread) const; 316 317 void evalFputc(const FnDescription *Desc, const CallEvent &Call, 318 CheckerContext &C) const; 319 320 void preFseek(const FnDescription *Desc, const CallEvent &Call, 321 CheckerContext &C) const; 322 void evalFseek(const FnDescription *Desc, const CallEvent &Call, 323 CheckerContext &C) const; 324 325 void evalFgetpos(const FnDescription *Desc, const CallEvent &Call, 326 CheckerContext &C) const; 327 328 void evalFsetpos(const FnDescription *Desc, const CallEvent &Call, 329 CheckerContext &C) const; 330 331 void evalFtell(const FnDescription *Desc, const CallEvent &Call, 332 CheckerContext &C) const; 333 334 void evalRewind(const FnDescription *Desc, const CallEvent &Call, 335 CheckerContext &C) const; 336 337 void preDefault(const FnDescription *Desc, const CallEvent &Call, 338 CheckerContext &C) const; 339 340 void evalClearerr(const FnDescription *Desc, const CallEvent &Call, 341 CheckerContext &C) const; 342 343 void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call, 344 CheckerContext &C, 345 const StreamErrorState &ErrorKind) const; 346 347 void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call, 348 CheckerContext &C, 349 const StreamErrorState &ErrorKind) const; 350 351 /// Check that the stream (in StreamVal) is not NULL. 352 /// If it can only be NULL a fatal error is emitted and nullptr returned. 353 /// Otherwise the return value is a new state where the stream is constrained 354 /// to be non-null. 355 ProgramStateRef ensureStreamNonNull(SVal StreamVal, const Expr *StreamE, 356 CheckerContext &C, 357 ProgramStateRef State) const; 358 359 /// Check that the stream is the opened state. 360 /// If the stream is known to be not opened an error is generated 361 /// and nullptr returned, otherwise the original state is returned. 362 ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C, 363 ProgramStateRef State) const; 364 365 /// Check that the stream has not an invalid ("indeterminate") file position, 366 /// generate warning for it. 367 /// (EOF is not an invalid position.) 368 /// The returned state can be nullptr if a fatal error was generated. 369 /// It can return non-null state if the stream has not an invalid position or 370 /// there is execution path with non-invalid position. 371 ProgramStateRef 372 ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C, 373 ProgramStateRef State) const; 374 375 /// Check the legality of the 'whence' argument of 'fseek'. 376 /// Generate error and return nullptr if it is found to be illegal. 377 /// Otherwise returns the state. 378 /// (State is not changed here because the "whence" value is already known.) 379 ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 380 ProgramStateRef State) const; 381 382 /// Generate warning about stream in EOF state. 383 /// There will be always a state transition into the passed State, 384 /// by the new non-fatal error node or (if failed) a normal transition, 385 /// to ensure uniform handling. 386 void reportFEofWarning(SymbolRef StreamSym, CheckerContext &C, 387 ProgramStateRef State) const; 388 389 /// Emit resource leak warnings for the given symbols. 390 /// Createn a non-fatal error node for these, and returns it (if any warnings 391 /// were generated). Return value is non-null. 392 ExplodedNode *reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms, 393 CheckerContext &C, ExplodedNode *Pred) const; 394 395 /// Find the description data of the function called by a call event. 396 /// Returns nullptr if no function is recognized. 397 const FnDescription *lookupFn(const CallEvent &Call) const { 398 // Recognize "global C functions" with only integral or pointer arguments 399 // (and matching name) as stream functions. 400 if (!Call.isGlobalCFunction()) 401 return nullptr; 402 for (auto *P : Call.parameters()) { 403 QualType T = P->getType(); 404 if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) 405 return nullptr; 406 } 407 408 return FnDescriptions.lookup(Call); 409 } 410 411 /// Generate a message for BugReporterVisitor if the stored symbol is 412 /// marked as interesting by the actual bug report. 413 const NoteTag *constructNoteTag(CheckerContext &C, SymbolRef StreamSym, 414 const std::string &Message) const { 415 return C.getNoteTag([this, StreamSym, 416 Message](PathSensitiveBugReport &BR) -> std::string { 417 if (BR.isInteresting(StreamSym) && &BR.getBugType() == &BT_ResourceLeak) 418 return Message; 419 return ""; 420 }); 421 } 422 423 const NoteTag *constructSetEofNoteTag(CheckerContext &C, 424 SymbolRef StreamSym) const { 425 return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) { 426 if (!BR.isInteresting(StreamSym) || 427 &BR.getBugType() != this->getBT_StreamEof()) 428 return ""; 429 430 BR.markNotInteresting(StreamSym); 431 432 return "Assuming stream reaches end-of-file here"; 433 }); 434 } 435 436 void initMacroValues(CheckerContext &C) const { 437 if (EofVal) 438 return; 439 440 if (const std::optional<int> OptInt = 441 tryExpandAsInteger("EOF", C.getPreprocessor())) 442 EofVal = *OptInt; 443 else 444 EofVal = -1; 445 if (const std::optional<int> OptInt = 446 tryExpandAsInteger("SEEK_SET", C.getPreprocessor())) 447 SeekSetVal = *OptInt; 448 if (const std::optional<int> OptInt = 449 tryExpandAsInteger("SEEK_END", C.getPreprocessor())) 450 SeekEndVal = *OptInt; 451 if (const std::optional<int> OptInt = 452 tryExpandAsInteger("SEEK_CUR", C.getPreprocessor())) 453 SeekCurVal = *OptInt; 454 } 455 456 /// Searches for the ExplodedNode where the file descriptor was acquired for 457 /// StreamSym. 458 static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N, 459 SymbolRef StreamSym, 460 CheckerContext &C); 461 }; 462 463 } // end anonymous namespace 464 465 // This map holds the state of a stream. 466 // The stream is identified with a SymbolRef that is created when a stream 467 // opening function is modeled by the checker. 468 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 469 470 inline void assertStreamStateOpened(const StreamState *SS) { 471 assert(SS->isOpened() && "Stream is expected to be opened"); 472 } 473 474 const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, 475 SymbolRef StreamSym, 476 CheckerContext &C) { 477 ProgramStateRef State = N->getState(); 478 // When bug type is resource leak, exploded node N may not have state info 479 // for leaked file descriptor, but predecessor should have it. 480 if (!State->get<StreamMap>(StreamSym)) 481 N = N->getFirstPred(); 482 483 const ExplodedNode *Pred = N; 484 while (N) { 485 State = N->getState(); 486 if (!State->get<StreamMap>(StreamSym)) 487 return Pred; 488 Pred = N; 489 N = N->getFirstPred(); 490 } 491 492 return nullptr; 493 } 494 495 //===----------------------------------------------------------------------===// 496 // Methods of StreamChecker. 497 //===----------------------------------------------------------------------===// 498 499 void StreamChecker::checkPreCall(const CallEvent &Call, 500 CheckerContext &C) const { 501 initMacroValues(C); 502 503 const FnDescription *Desc = lookupFn(Call); 504 if (!Desc || !Desc->PreFn) 505 return; 506 507 Desc->PreFn(this, Desc, Call, C); 508 } 509 510 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { 511 const FnDescription *Desc = lookupFn(Call); 512 if (!Desc && TestMode) 513 Desc = FnTestDescriptions.lookup(Call); 514 if (!Desc || !Desc->EvalFn) 515 return false; 516 517 Desc->EvalFn(this, Desc, Call, C); 518 519 return C.isDifferent(); 520 } 521 522 void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, 523 CheckerContext &C) const { 524 ProgramStateRef State = C.getState(); 525 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 526 if (!CE) 527 return; 528 529 DefinedSVal RetVal = makeRetVal(C, CE); 530 SymbolRef RetSym = RetVal.getAsSymbol(); 531 assert(RetSym && "RetVal must be a symbol here."); 532 533 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 534 535 // Bifurcate the state into two: one with a valid FILE* pointer, the other 536 // with a NULL. 537 ProgramStateRef StateNotNull, StateNull; 538 std::tie(StateNotNull, StateNull) = 539 C.getConstraintManager().assumeDual(State, RetVal); 540 541 StateNotNull = 542 StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc)); 543 StateNull = 544 StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc)); 545 546 C.addTransition(StateNotNull, 547 constructNoteTag(C, RetSym, "Stream opened here")); 548 C.addTransition(StateNull); 549 } 550 551 void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, 552 CheckerContext &C) const { 553 // Do not allow NULL as passed stream pointer but allow a closed stream. 554 ProgramStateRef State = C.getState(); 555 State = ensureStreamNonNull(getStreamArg(Desc, Call), 556 Call.getArgExpr(Desc->StreamArgNo), C, State); 557 if (!State) 558 return; 559 560 C.addTransition(State); 561 } 562 563 void StreamChecker::evalFreopen(const FnDescription *Desc, 564 const CallEvent &Call, 565 CheckerContext &C) const { 566 ProgramStateRef State = C.getState(); 567 568 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 569 if (!CE) 570 return; 571 572 std::optional<DefinedSVal> StreamVal = 573 getStreamArg(Desc, Call).getAs<DefinedSVal>(); 574 if (!StreamVal) 575 return; 576 577 SymbolRef StreamSym = StreamVal->getAsSymbol(); 578 // Do not care about concrete values for stream ("(FILE *)0x12345"?). 579 // FIXME: Can be stdin, stdout, stderr such values? 580 if (!StreamSym) 581 return; 582 583 // Do not handle untracked stream. It is probably escaped. 584 if (!State->get<StreamMap>(StreamSym)) 585 return; 586 587 // Generate state for non-failed case. 588 // Return value is the passed stream pointer. 589 // According to the documentations, the stream is closed first 590 // but any close error is ignored. The state changes to (or remains) opened. 591 ProgramStateRef StateRetNotNull = 592 State->BindExpr(CE, C.getLocationContext(), *StreamVal); 593 // Generate state for NULL return value. 594 // Stream switches to OpenFailed state. 595 ProgramStateRef StateRetNull = 596 State->BindExpr(CE, C.getLocationContext(), 597 C.getSValBuilder().makeNullWithType(CE->getType())); 598 599 StateRetNotNull = 600 StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); 601 StateRetNull = 602 StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc)); 603 604 C.addTransition(StateRetNotNull, 605 constructNoteTag(C, StreamSym, "Stream reopened here")); 606 C.addTransition(StateRetNull); 607 } 608 609 void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, 610 CheckerContext &C) const { 611 ProgramStateRef State = C.getState(); 612 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); 613 if (!Sym) 614 return; 615 616 const StreamState *SS = State->get<StreamMap>(Sym); 617 if (!SS) 618 return; 619 620 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 621 if (!CE) 622 return; 623 624 assertStreamStateOpened(SS); 625 626 // Close the File Descriptor. 627 // Regardless if the close fails or not, stream becomes "closed" 628 // and can not be used any more. 629 State = State->set<StreamMap>(Sym, StreamState::getClosed(Desc)); 630 631 // Return 0 on success, EOF on failure. 632 SValBuilder &SVB = C.getSValBuilder(); 633 ProgramStateRef StateSuccess = State->BindExpr( 634 CE, C.getLocationContext(), SVB.makeIntVal(0, C.getASTContext().IntTy)); 635 ProgramStateRef StateFailure = 636 State->BindExpr(CE, C.getLocationContext(), 637 SVB.makeIntVal(*EofVal, C.getASTContext().IntTy)); 638 639 C.addTransition(StateSuccess); 640 C.addTransition(StateFailure); 641 } 642 643 void StreamChecker::preReadWrite(const FnDescription *Desc, 644 const CallEvent &Call, CheckerContext &C, 645 bool IsRead) const { 646 ProgramStateRef State = C.getState(); 647 SVal StreamVal = getStreamArg(Desc, Call); 648 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C, 649 State); 650 if (!State) 651 return; 652 State = ensureStreamOpened(StreamVal, C, State); 653 if (!State) 654 return; 655 State = ensureNoFilePositionIndeterminate(StreamVal, C, State); 656 if (!State) 657 return; 658 659 if (!IsRead) { 660 C.addTransition(State); 661 return; 662 } 663 664 SymbolRef Sym = StreamVal.getAsSymbol(); 665 if (Sym && State->get<StreamMap>(Sym)) { 666 const StreamState *SS = State->get<StreamMap>(Sym); 667 if (SS->ErrorState & ErrorFEof) 668 reportFEofWarning(Sym, C, State); 669 } else { 670 C.addTransition(State); 671 } 672 } 673 674 void StreamChecker::evalFreadFwrite(const FnDescription *Desc, 675 const CallEvent &Call, CheckerContext &C, 676 bool IsFread) const { 677 ProgramStateRef State = C.getState(); 678 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 679 if (!StreamSym) 680 return; 681 682 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 683 if (!CE) 684 return; 685 686 std::optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>(); 687 if (!SizeVal) 688 return; 689 std::optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>(); 690 if (!NMembVal) 691 return; 692 693 const StreamState *OldSS = State->get<StreamMap>(StreamSym); 694 if (!OldSS) 695 return; 696 697 assertStreamStateOpened(OldSS); 698 699 // C'99 standard, §7.19.8.1.3, the return value of fread: 700 // The fread function returns the number of elements successfully read, which 701 // may be less than nmemb if a read error or end-of-file is encountered. If 702 // size or nmemb is zero, fread returns zero and the contents of the array and 703 // the state of the stream remain unchanged. 704 705 if (State->isNull(*SizeVal).isConstrainedTrue() || 706 State->isNull(*NMembVal).isConstrainedTrue()) { 707 // This is the "size or nmemb is zero" case. 708 // Just return 0, do nothing more (not clear the error flags). 709 State = bindInt(0, State, C, CE); 710 C.addTransition(State); 711 return; 712 } 713 714 // Generate a transition for the success state. 715 // If we know the state to be FEOF at fread, do not add a success state. 716 if (!IsFread || (OldSS->ErrorState != ErrorFEof)) { 717 ProgramStateRef StateNotFailed = 718 State->BindExpr(CE, C.getLocationContext(), *NMembVal); 719 StateNotFailed = 720 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); 721 C.addTransition(StateNotFailed); 722 } 723 724 // Add transition for the failed state. 725 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); 726 ProgramStateRef StateFailed = 727 State->BindExpr(CE, C.getLocationContext(), RetVal); 728 auto Cond = 729 C.getSValBuilder() 730 .evalBinOpNN(State, BO_LT, RetVal, *NMembVal, C.getASTContext().IntTy) 731 .getAs<DefinedOrUnknownSVal>(); 732 if (!Cond) 733 return; 734 StateFailed = StateFailed->assume(*Cond, true); 735 if (!StateFailed) 736 return; 737 738 StreamErrorState NewES; 739 if (IsFread) 740 NewES = 741 (OldSS->ErrorState == ErrorFEof) ? ErrorFEof : ErrorFEof | ErrorFError; 742 else 743 NewES = ErrorFError; 744 // If a (non-EOF) error occurs, the resulting value of the file position 745 // indicator for the stream is indeterminate. 746 StreamState NewSS = StreamState::getOpened(Desc, NewES, !NewES.isFEof()); 747 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS); 748 if (IsFread && OldSS->ErrorState != ErrorFEof) 749 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym)); 750 else 751 C.addTransition(StateFailed); 752 } 753 754 void StreamChecker::evalFputc(const FnDescription *Desc, const CallEvent &Call, 755 CheckerContext &C) const { 756 ProgramStateRef State = C.getState(); 757 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 758 if (!StreamSym) 759 return; 760 761 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 762 if (!CE) 763 return; 764 765 const StreamState *OldSS = State->get<StreamMap>(StreamSym); 766 if (!OldSS) 767 return; 768 769 assertStreamStateOpened(OldSS); 770 771 // `fputc` returns the written character on success, otherwise returns EOF. 772 773 // Generate a transition for the success state. 774 std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>(); 775 if (!PutVal) 776 return; 777 ProgramStateRef StateNotFailed = 778 State->BindExpr(CE, C.getLocationContext(), *PutVal); 779 StateNotFailed = 780 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); 781 C.addTransition(StateNotFailed); 782 783 // Add transition for the failed state. 784 // If a (non-EOF) error occurs, the resulting value of the file position 785 // indicator for the stream is indeterminate. 786 ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE); 787 StreamState NewSS = StreamState::getOpened( 788 Desc, ErrorFError, /*IsFilePositionIndeterminate*/ true); 789 StateFailed = StateFailed->set<StreamMap>(StreamSym, NewSS); 790 C.addTransition(StateFailed); 791 } 792 793 void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, 794 CheckerContext &C) const { 795 ProgramStateRef State = C.getState(); 796 SVal StreamVal = getStreamArg(Desc, Call); 797 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C, 798 State); 799 if (!State) 800 return; 801 State = ensureStreamOpened(StreamVal, C, State); 802 if (!State) 803 return; 804 State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State); 805 if (!State) 806 return; 807 808 C.addTransition(State); 809 } 810 811 void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call, 812 CheckerContext &C) const { 813 ProgramStateRef State = C.getState(); 814 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 815 if (!StreamSym) 816 return; 817 818 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 819 if (!CE) 820 return; 821 822 // Ignore the call if the stream is not tracked. 823 if (!State->get<StreamMap>(StreamSym)) 824 return; 825 826 const llvm::APSInt *PosV = 827 C.getSValBuilder().getKnownValue(State, Call.getArgSVal(1)); 828 const llvm::APSInt *WhenceV = 829 C.getSValBuilder().getKnownValue(State, Call.getArgSVal(2)); 830 831 DefinedSVal RetVal = makeRetVal(C, CE); 832 833 // Make expression result. 834 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 835 836 // Bifurcate the state into failed and non-failed. 837 // Return zero on success, nonzero on error. 838 ProgramStateRef StateNotFailed, StateFailed; 839 std::tie(StateFailed, StateNotFailed) = 840 C.getConstraintManager().assumeDual(State, RetVal); 841 842 // Reset the state to opened with no error. 843 StateNotFailed = 844 StateNotFailed->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); 845 // We get error. 846 // It is possible that fseek fails but sets none of the error flags. 847 // If fseek failed, assume that the file position becomes indeterminate in any 848 // case. 849 StreamErrorState NewErrS = ErrorNone | ErrorFError; 850 // Setting the position to start of file never produces EOF error. 851 if (!(PosV && *PosV == 0 && WhenceV && *WhenceV == SeekSetVal)) 852 NewErrS = NewErrS | ErrorFEof; 853 StateFailed = StateFailed->set<StreamMap>( 854 StreamSym, StreamState::getOpened(Desc, NewErrS, true)); 855 856 C.addTransition(StateNotFailed); 857 C.addTransition(StateFailed, constructSetEofNoteTag(C, StreamSym)); 858 } 859 860 void StreamChecker::evalFgetpos(const FnDescription *Desc, 861 const CallEvent &Call, 862 CheckerContext &C) const { 863 ProgramStateRef State = C.getState(); 864 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); 865 if (!Sym) 866 return; 867 868 // Do not evaluate if stream is not found. 869 if (!State->get<StreamMap>(Sym)) 870 return; 871 872 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 873 if (!CE) 874 return; 875 876 DefinedSVal RetVal = makeRetVal(C, CE); 877 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 878 ProgramStateRef StateNotFailed, StateFailed; 879 std::tie(StateFailed, StateNotFailed) = 880 C.getConstraintManager().assumeDual(State, RetVal); 881 882 // This function does not affect the stream state. 883 // Still we add success and failure state with the appropriate return value. 884 // StdLibraryFunctionsChecker can change these states (set the 'errno' state). 885 C.addTransition(StateNotFailed); 886 C.addTransition(StateFailed); 887 } 888 889 void StreamChecker::evalFsetpos(const FnDescription *Desc, 890 const CallEvent &Call, 891 CheckerContext &C) const { 892 ProgramStateRef State = C.getState(); 893 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 894 if (!StreamSym) 895 return; 896 897 const StreamState *SS = State->get<StreamMap>(StreamSym); 898 if (!SS) 899 return; 900 901 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 902 if (!CE) 903 return; 904 905 assertStreamStateOpened(SS); 906 907 DefinedSVal RetVal = makeRetVal(C, CE); 908 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 909 ProgramStateRef StateNotFailed, StateFailed; 910 std::tie(StateFailed, StateNotFailed) = 911 C.getConstraintManager().assumeDual(State, RetVal); 912 913 StateNotFailed = StateNotFailed->set<StreamMap>( 914 StreamSym, StreamState::getOpened(Desc, ErrorNone, false)); 915 916 // At failure ferror could be set. 917 // The standards do not tell what happens with the file position at failure. 918 // But we can assume that it is dangerous to make a next I/O operation after 919 // the position was not set correctly (similar to 'fseek'). 920 StateFailed = StateFailed->set<StreamMap>( 921 StreamSym, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true)); 922 923 C.addTransition(StateNotFailed); 924 C.addTransition(StateFailed); 925 } 926 927 void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call, 928 CheckerContext &C) const { 929 ProgramStateRef State = C.getState(); 930 SymbolRef Sym = getStreamArg(Desc, Call).getAsSymbol(); 931 if (!Sym) 932 return; 933 934 if (!State->get<StreamMap>(Sym)) 935 return; 936 937 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 938 if (!CE) 939 return; 940 941 SValBuilder &SVB = C.getSValBuilder(); 942 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); 943 ProgramStateRef StateNotFailed = 944 State->BindExpr(CE, C.getLocationContext(), RetVal); 945 auto Cond = SVB.evalBinOp(State, BO_GE, RetVal, 946 SVB.makeZeroVal(C.getASTContext().LongTy), 947 SVB.getConditionType()) 948 .getAs<DefinedOrUnknownSVal>(); 949 if (!Cond) 950 return; 951 StateNotFailed = StateNotFailed->assume(*Cond, true); 952 if (!StateNotFailed) 953 return; 954 955 ProgramStateRef StateFailed = State->BindExpr( 956 CE, C.getLocationContext(), SVB.makeIntVal(-1, C.getASTContext().LongTy)); 957 958 C.addTransition(StateNotFailed); 959 C.addTransition(StateFailed); 960 } 961 962 void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call, 963 CheckerContext &C) const { 964 ProgramStateRef State = C.getState(); 965 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 966 if (!StreamSym) 967 return; 968 969 const StreamState *SS = State->get<StreamMap>(StreamSym); 970 if (!SS) 971 return; 972 973 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 974 if (!CE) 975 return; 976 977 assertStreamStateOpened(SS); 978 979 State = State->set<StreamMap>(StreamSym, 980 StreamState::getOpened(Desc, ErrorNone, false)); 981 982 C.addTransition(State); 983 } 984 985 void StreamChecker::evalClearerr(const FnDescription *Desc, 986 const CallEvent &Call, 987 CheckerContext &C) const { 988 ProgramStateRef State = C.getState(); 989 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 990 if (!StreamSym) 991 return; 992 993 const StreamState *SS = State->get<StreamMap>(StreamSym); 994 if (!SS) 995 return; 996 997 assertStreamStateOpened(SS); 998 999 // FilePositionIndeterminate is not cleared. 1000 State = State->set<StreamMap>( 1001 StreamSym, 1002 StreamState::getOpened(Desc, ErrorNone, SS->FilePositionIndeterminate)); 1003 C.addTransition(State); 1004 } 1005 1006 void StreamChecker::evalFeofFerror(const FnDescription *Desc, 1007 const CallEvent &Call, CheckerContext &C, 1008 const StreamErrorState &ErrorKind) const { 1009 ProgramStateRef State = C.getState(); 1010 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 1011 if (!StreamSym) 1012 return; 1013 1014 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 1015 if (!CE) 1016 return; 1017 1018 const StreamState *SS = State->get<StreamMap>(StreamSym); 1019 if (!SS) 1020 return; 1021 1022 assertStreamStateOpened(SS); 1023 1024 if (SS->ErrorState & ErrorKind) { 1025 // Execution path with error of ErrorKind. 1026 // Function returns true. 1027 // From now on it is the only one error state. 1028 ProgramStateRef TrueState = bindAndAssumeTrue(State, C, CE); 1029 C.addTransition(TrueState->set<StreamMap>( 1030 StreamSym, StreamState::getOpened(Desc, ErrorKind, 1031 SS->FilePositionIndeterminate && 1032 !ErrorKind.isFEof()))); 1033 } 1034 if (StreamErrorState NewES = SS->ErrorState & (~ErrorKind)) { 1035 // Execution path(s) with ErrorKind not set. 1036 // Function returns false. 1037 // New error state is everything before minus ErrorKind. 1038 ProgramStateRef FalseState = bindInt(0, State, C, CE); 1039 C.addTransition(FalseState->set<StreamMap>( 1040 StreamSym, 1041 StreamState::getOpened( 1042 Desc, NewES, SS->FilePositionIndeterminate && !NewES.isFEof()))); 1043 } 1044 } 1045 1046 void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call, 1047 CheckerContext &C) const { 1048 ProgramStateRef State = C.getState(); 1049 SVal StreamVal = getStreamArg(Desc, Call); 1050 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C, 1051 State); 1052 if (!State) 1053 return; 1054 State = ensureStreamOpened(StreamVal, C, State); 1055 if (!State) 1056 return; 1057 1058 C.addTransition(State); 1059 } 1060 1061 void StreamChecker::evalSetFeofFerror(const FnDescription *Desc, 1062 const CallEvent &Call, CheckerContext &C, 1063 const StreamErrorState &ErrorKind) const { 1064 ProgramStateRef State = C.getState(); 1065 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 1066 assert(StreamSym && "Operation not permitted on non-symbolic stream value."); 1067 const StreamState *SS = State->get<StreamMap>(StreamSym); 1068 assert(SS && "Stream should be tracked by the checker."); 1069 State = State->set<StreamMap>( 1070 StreamSym, StreamState::getOpened(SS->LastOperation, ErrorKind)); 1071 C.addTransition(State); 1072 } 1073 1074 ProgramStateRef 1075 StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE, 1076 CheckerContext &C, 1077 ProgramStateRef State) const { 1078 auto Stream = StreamVal.getAs<DefinedSVal>(); 1079 if (!Stream) 1080 return State; 1081 1082 ConstraintManager &CM = C.getConstraintManager(); 1083 1084 ProgramStateRef StateNotNull, StateNull; 1085 std::tie(StateNotNull, StateNull) = CM.assumeDual(State, *Stream); 1086 1087 if (!StateNotNull && StateNull) { 1088 if (ExplodedNode *N = C.generateErrorNode(StateNull)) { 1089 auto R = std::make_unique<PathSensitiveBugReport>( 1090 BT_FileNull, "Stream pointer might be NULL.", N); 1091 if (StreamE) 1092 bugreporter::trackExpressionValue(N, StreamE, *R); 1093 C.emitReport(std::move(R)); 1094 } 1095 return nullptr; 1096 } 1097 1098 return StateNotNull; 1099 } 1100 1101 ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal, 1102 CheckerContext &C, 1103 ProgramStateRef State) const { 1104 SymbolRef Sym = StreamVal.getAsSymbol(); 1105 if (!Sym) 1106 return State; 1107 1108 const StreamState *SS = State->get<StreamMap>(Sym); 1109 if (!SS) 1110 return State; 1111 1112 if (SS->isClosed()) { 1113 // Using a stream pointer after 'fclose' causes undefined behavior 1114 // according to cppreference.com . 1115 ExplodedNode *N = C.generateErrorNode(); 1116 if (N) { 1117 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1118 BT_UseAfterClose, 1119 "Stream might be already closed. Causes undefined behaviour.", N)); 1120 return nullptr; 1121 } 1122 1123 return State; 1124 } 1125 1126 if (SS->isOpenFailed()) { 1127 // Using a stream that has failed to open is likely to cause problems. 1128 // This should usually not occur because stream pointer is NULL. 1129 // But freopen can cause a state when stream pointer remains non-null but 1130 // failed to open. 1131 ExplodedNode *N = C.generateErrorNode(); 1132 if (N) { 1133 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1134 BT_UseAfterOpenFailed, 1135 "Stream might be invalid after " 1136 "(re-)opening it has failed. " 1137 "Can cause undefined behaviour.", 1138 N)); 1139 return nullptr; 1140 } 1141 } 1142 1143 return State; 1144 } 1145 1146 ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate( 1147 SVal StreamVal, CheckerContext &C, ProgramStateRef State) const { 1148 static const char *BugMessage = 1149 "File position of the stream might be 'indeterminate' " 1150 "after a failed operation. " 1151 "Can cause undefined behavior."; 1152 1153 SymbolRef Sym = StreamVal.getAsSymbol(); 1154 if (!Sym) 1155 return State; 1156 1157 const StreamState *SS = State->get<StreamMap>(Sym); 1158 if (!SS) 1159 return State; 1160 1161 assert(SS->isOpened() && "First ensure that stream is opened."); 1162 1163 if (SS->FilePositionIndeterminate) { 1164 if (SS->ErrorState & ErrorFEof) { 1165 // The error is unknown but may be FEOF. 1166 // Continue analysis with the FEOF error state. 1167 // Report warning because the other possible error states. 1168 ExplodedNode *N = C.generateNonFatalErrorNode(State); 1169 if (!N) 1170 return nullptr; 1171 1172 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1173 BT_IndeterminatePosition, BugMessage, N)); 1174 return State->set<StreamMap>( 1175 Sym, StreamState::getOpened(SS->LastOperation, ErrorFEof, false)); 1176 } 1177 1178 // Known or unknown error state without FEOF possible. 1179 // Stop analysis, report error. 1180 ExplodedNode *N = C.generateErrorNode(State); 1181 if (N) 1182 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1183 BT_IndeterminatePosition, BugMessage, N)); 1184 1185 return nullptr; 1186 } 1187 1188 return State; 1189 } 1190 1191 ProgramStateRef 1192 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 1193 ProgramStateRef State) const { 1194 std::optional<nonloc::ConcreteInt> CI = 1195 WhenceVal.getAs<nonloc::ConcreteInt>(); 1196 if (!CI) 1197 return State; 1198 1199 int64_t X = CI->getValue().getSExtValue(); 1200 if (X == SeekSetVal || X == SeekCurVal || X == SeekEndVal) 1201 return State; 1202 1203 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 1204 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1205 BT_IllegalWhence, 1206 "The whence argument to fseek() should be " 1207 "SEEK_SET, SEEK_END, or SEEK_CUR.", 1208 N)); 1209 return nullptr; 1210 } 1211 1212 return State; 1213 } 1214 1215 void StreamChecker::reportFEofWarning(SymbolRef StreamSym, CheckerContext &C, 1216 ProgramStateRef State) const { 1217 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 1218 auto R = std::make_unique<PathSensitiveBugReport>( 1219 BT_StreamEof, 1220 "Read function called when stream is in EOF state. " 1221 "Function has no effect.", 1222 N); 1223 R->markInteresting(StreamSym); 1224 C.emitReport(std::move(R)); 1225 return; 1226 } 1227 C.addTransition(State); 1228 } 1229 1230 ExplodedNode * 1231 StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms, 1232 CheckerContext &C, ExplodedNode *Pred) const { 1233 ExplodedNode *Err = C.generateNonFatalErrorNode(C.getState(), Pred); 1234 if (!Err) 1235 return Pred; 1236 1237 for (SymbolRef LeakSym : LeakedSyms) { 1238 // Resource leaks can result in multiple warning that describe the same kind 1239 // of programming error: 1240 // void f() { 1241 // FILE *F = fopen("a.txt"); 1242 // if (rand()) // state split 1243 // return; // warning 1244 // } // warning 1245 // While this isn't necessarily true (leaking the same stream could result 1246 // from a different kinds of errors), the reduction in redundant reports 1247 // makes this a worthwhile heuristic. 1248 // FIXME: Add a checker option to turn this uniqueing feature off. 1249 const ExplodedNode *StreamOpenNode = getAcquisitionSite(Err, LeakSym, C); 1250 assert(StreamOpenNode && "Could not find place of stream opening."); 1251 1252 PathDiagnosticLocation LocUsedForUniqueing; 1253 if (const Stmt *StreamStmt = StreamOpenNode->getStmtForDiagnostics()) 1254 LocUsedForUniqueing = PathDiagnosticLocation::createBegin( 1255 StreamStmt, C.getSourceManager(), 1256 StreamOpenNode->getLocationContext()); 1257 1258 std::unique_ptr<PathSensitiveBugReport> R = 1259 std::make_unique<PathSensitiveBugReport>( 1260 BT_ResourceLeak, 1261 "Opened stream never closed. Potential resource leak.", Err, 1262 LocUsedForUniqueing, 1263 StreamOpenNode->getLocationContext()->getDecl()); 1264 R->markInteresting(LeakSym); 1265 C.emitReport(std::move(R)); 1266 } 1267 1268 return Err; 1269 } 1270 1271 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 1272 CheckerContext &C) const { 1273 ProgramStateRef State = C.getState(); 1274 1275 llvm::SmallVector<SymbolRef, 2> LeakedSyms; 1276 1277 const StreamMapTy &Map = State->get<StreamMap>(); 1278 for (const auto &I : Map) { 1279 SymbolRef Sym = I.first; 1280 const StreamState &SS = I.second; 1281 if (!SymReaper.isDead(Sym)) 1282 continue; 1283 if (SS.isOpened()) 1284 LeakedSyms.push_back(Sym); 1285 State = State->remove<StreamMap>(Sym); 1286 } 1287 1288 ExplodedNode *N = C.getPredecessor(); 1289 if (!LeakedSyms.empty()) 1290 N = reportLeaks(LeakedSyms, C, N); 1291 1292 C.addTransition(State, N); 1293 } 1294 1295 ProgramStateRef StreamChecker::checkPointerEscape( 1296 ProgramStateRef State, const InvalidatedSymbols &Escaped, 1297 const CallEvent *Call, PointerEscapeKind Kind) const { 1298 // Check for file-handling system call that is not handled by the checker. 1299 // FIXME: The checker should be updated to handle all system calls that take 1300 // 'FILE*' argument. These are now ignored. 1301 if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader()) 1302 return State; 1303 1304 for (SymbolRef Sym : Escaped) { 1305 // The symbol escaped. 1306 // From now the stream can be manipulated in unknown way to the checker, 1307 // it is not possible to handle it any more. 1308 // Optimistically, assume that the corresponding file handle will be closed 1309 // somewhere else. 1310 // Remove symbol from state so the following stream calls on this symbol are 1311 // not handled by the checker. 1312 State = State->remove<StreamMap>(Sym); 1313 } 1314 return State; 1315 } 1316 1317 //===----------------------------------------------------------------------===// 1318 // Checker registration. 1319 //===----------------------------------------------------------------------===// 1320 1321 void ento::registerStreamChecker(CheckerManager &Mgr) { 1322 Mgr.registerChecker<StreamChecker>(); 1323 } 1324 1325 bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) { 1326 return true; 1327 } 1328 1329 void ento::registerStreamTesterChecker(CheckerManager &Mgr) { 1330 auto *Checker = Mgr.getChecker<StreamChecker>(); 1331 Checker->TestMode = true; 1332 } 1333 1334 bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) { 1335 return true; 1336 } 1337