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