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