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