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