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