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