1 //===-- StreamChecker.cpp -----------------------------------------*- C++ -*--// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file defines checkers that model and check stream handling functions. 10 // 11 //===----------------------------------------------------------------------===// 12 13 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" 14 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 15 #include "clang/StaticAnalyzer/Core/Checker.h" 16 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 17 #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" 18 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" 19 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 20 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" 21 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" 22 #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" 23 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" 24 #include <functional> 25 #include <optional> 26 27 using namespace clang; 28 using namespace ento; 29 using namespace std::placeholders; 30 31 //===----------------------------------------------------------------------===// 32 // Definition of state data structures. 33 //===----------------------------------------------------------------------===// 34 35 namespace { 36 37 struct FnDescription; 38 39 /// State of the stream error flags. 40 /// Sometimes it is not known to the checker what error flags are set. 41 /// This is indicated by setting more than one flag to true. 42 /// This is an optimization to avoid state splits. 43 /// A stream can either be in FEOF or FERROR but not both at the same time. 44 /// Multiple flags are set to handle the corresponding states together. 45 struct StreamErrorState { 46 /// The stream can be in state where none of the error flags set. 47 bool NoError = true; 48 /// The stream can be in state where the EOF indicator is set. 49 bool FEof = false; 50 /// The stream can be in state where the error indicator is set. 51 bool FError = false; 52 53 bool isNoError() const { return NoError && !FEof && !FError; } 54 bool isFEof() const { return !NoError && FEof && !FError; } 55 bool isFError() const { return !NoError && !FEof && FError; } 56 57 bool operator==(const StreamErrorState &ES) const { 58 return NoError == ES.NoError && FEof == ES.FEof && FError == ES.FError; 59 } 60 61 bool operator!=(const StreamErrorState &ES) const { return !(*this == ES); } 62 63 StreamErrorState operator|(const StreamErrorState &E) const { 64 return {NoError || E.NoError, FEof || E.FEof, FError || E.FError}; 65 } 66 67 StreamErrorState operator&(const StreamErrorState &E) const { 68 return {NoError && E.NoError, FEof && E.FEof, FError && E.FError}; 69 } 70 71 StreamErrorState operator~() const { return {!NoError, !FEof, !FError}; } 72 73 /// Returns if the StreamErrorState is a valid object. 74 operator bool() const { return NoError || FEof || FError; } 75 76 void Profile(llvm::FoldingSetNodeID &ID) const { 77 ID.AddBoolean(NoError); 78 ID.AddBoolean(FEof); 79 ID.AddBoolean(FError); 80 } 81 }; 82 83 const StreamErrorState ErrorNone{true, false, false}; 84 const StreamErrorState ErrorFEof{false, true, false}; 85 const StreamErrorState ErrorFError{false, false, true}; 86 87 /// Full state information about a stream pointer. 88 struct StreamState { 89 /// The last file operation called in the stream. 90 /// Can be nullptr. 91 const FnDescription *LastOperation; 92 93 /// State of a stream symbol. 94 enum KindTy { 95 Opened, /// Stream is opened. 96 Closed, /// Closed stream (an invalid stream pointer after it was closed). 97 OpenFailed /// The last open operation has failed. 98 } State; 99 100 /// State of the error flags. 101 /// Ignored in non-opened stream state but must be NoError. 102 StreamErrorState const ErrorState; 103 104 /// Indicate if the file has an "indeterminate file position indicator". 105 /// This can be set at a failing read or write or seek operation. 106 /// If it is set no more read or write is allowed. 107 /// This value is not dependent on the stream error flags: 108 /// The error flag may be cleared with `clearerr` but the file position 109 /// remains still indeterminate. 110 /// This value applies to all error states in ErrorState except FEOF. 111 /// An EOF+indeterminate state is the same as EOF state. 112 bool const FilePositionIndeterminate = false; 113 114 StreamState(const FnDescription *L, KindTy S, const StreamErrorState &ES, 115 bool IsFilePositionIndeterminate) 116 : LastOperation(L), State(S), ErrorState(ES), 117 FilePositionIndeterminate(IsFilePositionIndeterminate) { 118 assert((!ES.isFEof() || !IsFilePositionIndeterminate) && 119 "FilePositionIndeterminate should be false in FEof case."); 120 assert((State == Opened || ErrorState.isNoError()) && 121 "ErrorState should be None in non-opened stream state."); 122 } 123 124 bool isOpened() const { return State == Opened; } 125 bool isClosed() const { return State == Closed; } 126 bool isOpenFailed() const { return State == OpenFailed; } 127 128 bool operator==(const StreamState &X) const { 129 // In not opened state error state should always NoError, so comparison 130 // here is no problem. 131 return LastOperation == X.LastOperation && State == X.State && 132 ErrorState == X.ErrorState && 133 FilePositionIndeterminate == X.FilePositionIndeterminate; 134 } 135 136 static StreamState getOpened(const FnDescription *L, 137 const StreamErrorState &ES = ErrorNone, 138 bool IsFilePositionIndeterminate = false) { 139 return StreamState{L, Opened, ES, IsFilePositionIndeterminate}; 140 } 141 static StreamState getClosed(const FnDescription *L) { 142 return StreamState{L, Closed, {}, false}; 143 } 144 static StreamState getOpenFailed(const FnDescription *L) { 145 return StreamState{L, OpenFailed, {}, false}; 146 } 147 148 void Profile(llvm::FoldingSetNodeID &ID) const { 149 ID.AddPointer(LastOperation); 150 ID.AddInteger(State); 151 ErrorState.Profile(ID); 152 ID.AddBoolean(FilePositionIndeterminate); 153 } 154 }; 155 156 } // namespace 157 158 // This map holds the state of a stream. 159 // The stream is identified with a SymbolRef that is created when a stream 160 // opening function is modeled by the checker. 161 REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) 162 163 //===----------------------------------------------------------------------===// 164 // StreamChecker class and utility functions. 165 //===----------------------------------------------------------------------===// 166 167 namespace { 168 169 class StreamChecker; 170 using FnCheck = std::function<void(const StreamChecker *, const FnDescription *, 171 const CallEvent &, CheckerContext &)>; 172 173 using ArgNoTy = unsigned int; 174 static const ArgNoTy ArgNone = std::numeric_limits<ArgNoTy>::max(); 175 176 struct FnDescription { 177 FnCheck PreFn; 178 FnCheck EvalFn; 179 ArgNoTy StreamArgNo; 180 }; 181 182 /// Get the value of the stream argument out of the passed call event. 183 /// The call should contain a function that is described by Desc. 184 SVal getStreamArg(const FnDescription *Desc, const CallEvent &Call) { 185 assert(Desc && Desc->StreamArgNo != ArgNone && 186 "Try to get a non-existing stream argument."); 187 return Call.getArgSVal(Desc->StreamArgNo); 188 } 189 190 /// Create a conjured symbol return value for a call expression. 191 DefinedSVal makeRetVal(CheckerContext &C, const CallExpr *CE) { 192 assert(CE && "Expecting a call expression."); 193 194 const LocationContext *LCtx = C.getLocationContext(); 195 return C.getSValBuilder() 196 .conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) 197 .castAs<DefinedSVal>(); 198 } 199 200 ProgramStateRef bindAndAssumeTrue(ProgramStateRef State, CheckerContext &C, 201 const CallExpr *CE) { 202 DefinedSVal RetVal = makeRetVal(C, CE); 203 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 204 State = State->assume(RetVal, true); 205 assert(State && "Assumption on new value should not fail."); 206 return State; 207 } 208 209 ProgramStateRef bindInt(uint64_t Value, ProgramStateRef State, 210 CheckerContext &C, const CallExpr *CE) { 211 State = State->BindExpr(CE, C.getLocationContext(), 212 C.getSValBuilder().makeIntVal(Value, CE->getType())); 213 return State; 214 } 215 216 inline void assertStreamStateOpened(const StreamState *SS) { 217 assert(SS->isOpened() && "Stream is expected to be opened"); 218 } 219 220 struct StreamOperationEvaluator { 221 SValBuilder &SVB; 222 const ASTContext &ACtx; 223 224 SymbolRef StreamSym; 225 const StreamState *SS = nullptr; 226 const CallExpr *CE = nullptr; 227 228 StreamOperationEvaluator(CheckerContext &C) 229 : SVB(C.getSValBuilder()), ACtx(C.getASTContext()) { 230 ; 231 } 232 233 bool Init(const FnDescription *Desc, const CallEvent &Call, CheckerContext &C, 234 ProgramStateRef State) { 235 StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 236 if (!StreamSym) 237 return false; 238 SS = State->get<StreamMap>(StreamSym); 239 if (!SS) 240 return false; 241 CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 242 if (!CE) 243 return false; 244 245 assertStreamStateOpened(SS); 246 247 return true; 248 } 249 250 bool isStreamEof() const { return SS->ErrorState == ErrorFEof; } 251 252 NonLoc getZeroVal(const CallEvent &Call) { 253 return *SVB.makeZeroVal(Call.getResultType()).getAs<NonLoc>(); 254 } 255 256 ProgramStateRef setStreamState(ProgramStateRef State, 257 const StreamState &NewSS) { 258 return State->set<StreamMap>(StreamSym, NewSS); 259 } 260 261 ProgramStateRef makeAndBindRetVal(ProgramStateRef State, CheckerContext &C) { 262 NonLoc RetVal = makeRetVal(C, CE).castAs<NonLoc>(); 263 return State->BindExpr(CE, C.getLocationContext(), RetVal); 264 } 265 266 ProgramStateRef bindReturnValue(ProgramStateRef State, CheckerContext &C, 267 uint64_t Val) { 268 return State->BindExpr(CE, C.getLocationContext(), 269 SVB.makeIntVal(Val, CE->getCallReturnType(ACtx))); 270 } 271 272 ProgramStateRef bindReturnValue(ProgramStateRef State, CheckerContext &C, 273 SVal Val) { 274 return State->BindExpr(CE, C.getLocationContext(), Val); 275 } 276 277 ProgramStateRef bindNullReturnValue(ProgramStateRef State, 278 CheckerContext &C) { 279 return State->BindExpr(CE, C.getLocationContext(), 280 C.getSValBuilder().makeNullWithType(CE->getType())); 281 } 282 283 ProgramStateRef assumeBinOpNN(ProgramStateRef State, 284 BinaryOperator::Opcode Op, NonLoc LHS, 285 NonLoc RHS) { 286 auto Cond = SVB.evalBinOpNN(State, Op, LHS, RHS, SVB.getConditionType()) 287 .getAs<DefinedOrUnknownSVal>(); 288 if (!Cond) 289 return nullptr; 290 return State->assume(*Cond, true); 291 } 292 293 ConstraintManager::ProgramStatePair 294 makeRetValAndAssumeDual(ProgramStateRef State, CheckerContext &C) { 295 DefinedSVal RetVal = makeRetVal(C, CE); 296 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 297 return C.getConstraintManager().assumeDual(State, RetVal); 298 } 299 }; 300 301 class StreamChecker : public Checker<check::PreCall, eval::Call, 302 check::DeadSymbols, check::PointerEscape> { 303 BugType BT_FileNull{this, "NULL stream pointer", "Stream handling error"}; 304 BugType BT_UseAfterClose{this, "Closed stream", "Stream handling error"}; 305 BugType BT_UseAfterOpenFailed{this, "Invalid stream", 306 "Stream handling error"}; 307 BugType BT_IndeterminatePosition{this, "Invalid stream state", 308 "Stream handling error"}; 309 BugType BT_IllegalWhence{this, "Illegal whence argument", 310 "Stream handling error"}; 311 BugType BT_StreamEof{this, "Stream already in EOF", "Stream handling error"}; 312 BugType BT_ResourceLeak{this, "Resource leak", "Stream handling error", 313 /*SuppressOnSink =*/true}; 314 315 public: 316 void checkPreCall(const CallEvent &Call, CheckerContext &C) const; 317 bool evalCall(const CallEvent &Call, CheckerContext &C) const; 318 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; 319 ProgramStateRef checkPointerEscape(ProgramStateRef State, 320 const InvalidatedSymbols &Escaped, 321 const CallEvent *Call, 322 PointerEscapeKind Kind) const; 323 324 /// If true, evaluate special testing stream functions. 325 bool TestMode = false; 326 327 const BugType *getBT_StreamEof() const { return &BT_StreamEof; } 328 329 private: 330 CallDescriptionMap<FnDescription> FnDescriptions = { 331 {{{"fopen"}, 2}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, 332 {{{"fdopen"}, 2}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, 333 {{{"freopen"}, 3}, 334 {&StreamChecker::preFreopen, &StreamChecker::evalFreopen, 2}}, 335 {{{"tmpfile"}, 0}, {nullptr, &StreamChecker::evalFopen, ArgNone}}, 336 {{{"fclose"}, 1}, 337 {&StreamChecker::preDefault, &StreamChecker::evalFclose, 0}}, 338 {{{"fread"}, 4}, 339 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), 340 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, true), 3}}, 341 {{{"fwrite"}, 4}, 342 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), 343 std::bind(&StreamChecker::evalFreadFwrite, _1, _2, _3, _4, false), 3}}, 344 {{{"fgetc"}, 1}, 345 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), 346 std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, true), 0}}, 347 {{{"fgets"}, 3}, 348 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), 349 std::bind(&StreamChecker::evalFgetx, _1, _2, _3, _4, false), 2}}, 350 {{{"fputc"}, 2}, 351 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), 352 std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, true), 1}}, 353 {{{"fputs"}, 2}, 354 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), 355 std::bind(&StreamChecker::evalFputx, _1, _2, _3, _4, false), 1}}, 356 {{{"fprintf"}}, 357 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), 358 std::bind(&StreamChecker::evalFprintf, _1, _2, _3, _4), 0}}, 359 {{{"fscanf"}}, 360 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), 361 std::bind(&StreamChecker::evalFscanf, _1, _2, _3, _4), 0}}, 362 {{{"ungetc"}, 2}, 363 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, false), 364 std::bind(&StreamChecker::evalUngetc, _1, _2, _3, _4), 1}}, 365 {{{"getdelim"}, 4}, 366 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), 367 std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 3}}, 368 {{{"getline"}, 3}, 369 {std::bind(&StreamChecker::preReadWrite, _1, _2, _3, _4, true), 370 std::bind(&StreamChecker::evalGetdelim, _1, _2, _3, _4), 2}}, 371 {{{"fseek"}, 3}, 372 {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}}, 373 {{{"fseeko"}, 3}, 374 {&StreamChecker::preFseek, &StreamChecker::evalFseek, 0}}, 375 {{{"ftell"}, 1}, 376 {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}}, 377 {{{"ftello"}, 1}, 378 {&StreamChecker::preDefault, &StreamChecker::evalFtell, 0}}, 379 {{{"fflush"}, 1}, 380 {&StreamChecker::preFflush, &StreamChecker::evalFflush, 0}}, 381 {{{"rewind"}, 1}, 382 {&StreamChecker::preDefault, &StreamChecker::evalRewind, 0}}, 383 {{{"fgetpos"}, 2}, 384 {&StreamChecker::preDefault, &StreamChecker::evalFgetpos, 0}}, 385 {{{"fsetpos"}, 2}, 386 {&StreamChecker::preDefault, &StreamChecker::evalFsetpos, 0}}, 387 {{{"clearerr"}, 1}, 388 {&StreamChecker::preDefault, &StreamChecker::evalClearerr, 0}}, 389 {{{"feof"}, 1}, 390 {&StreamChecker::preDefault, 391 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFEof), 392 0}}, 393 {{{"ferror"}, 1}, 394 {&StreamChecker::preDefault, 395 std::bind(&StreamChecker::evalFeofFerror, _1, _2, _3, _4, ErrorFError), 396 0}}, 397 {{{"fileno"}, 1}, 398 {&StreamChecker::preDefault, &StreamChecker::evalFileno, 0}}, 399 }; 400 401 CallDescriptionMap<FnDescription> FnTestDescriptions = { 402 {{{"StreamTesterChecker_make_feof_stream"}, 1}, 403 {nullptr, 404 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, ErrorFEof), 405 0}}, 406 {{{"StreamTesterChecker_make_ferror_stream"}, 1}, 407 {nullptr, 408 std::bind(&StreamChecker::evalSetFeofFerror, _1, _2, _3, _4, 409 ErrorFError), 410 0}}, 411 }; 412 413 /// Expanded value of EOF, empty before initialization. 414 mutable std::optional<int> EofVal; 415 /// Expanded value of SEEK_SET, 0 if not found. 416 mutable int SeekSetVal = 0; 417 /// Expanded value of SEEK_CUR, 1 if not found. 418 mutable int SeekCurVal = 1; 419 /// Expanded value of SEEK_END, 2 if not found. 420 mutable int SeekEndVal = 2; 421 422 void evalFopen(const FnDescription *Desc, const CallEvent &Call, 423 CheckerContext &C) const; 424 425 void preFreopen(const FnDescription *Desc, const CallEvent &Call, 426 CheckerContext &C) const; 427 void evalFreopen(const FnDescription *Desc, const CallEvent &Call, 428 CheckerContext &C) const; 429 430 void evalFclose(const FnDescription *Desc, const CallEvent &Call, 431 CheckerContext &C) const; 432 433 void preReadWrite(const FnDescription *Desc, const CallEvent &Call, 434 CheckerContext &C, bool IsRead) const; 435 436 void evalFreadFwrite(const FnDescription *Desc, const CallEvent &Call, 437 CheckerContext &C, bool IsFread) const; 438 439 void evalFgetx(const FnDescription *Desc, const CallEvent &Call, 440 CheckerContext &C, bool SingleChar) const; 441 442 void evalFputx(const FnDescription *Desc, const CallEvent &Call, 443 CheckerContext &C, bool IsSingleChar) const; 444 445 void evalFprintf(const FnDescription *Desc, const CallEvent &Call, 446 CheckerContext &C) const; 447 448 void evalFscanf(const FnDescription *Desc, const CallEvent &Call, 449 CheckerContext &C) const; 450 451 void evalUngetc(const FnDescription *Desc, const CallEvent &Call, 452 CheckerContext &C) const; 453 454 void evalGetdelim(const FnDescription *Desc, const CallEvent &Call, 455 CheckerContext &C) const; 456 457 void preFseek(const FnDescription *Desc, const CallEvent &Call, 458 CheckerContext &C) const; 459 void evalFseek(const FnDescription *Desc, const CallEvent &Call, 460 CheckerContext &C) const; 461 462 void evalFgetpos(const FnDescription *Desc, const CallEvent &Call, 463 CheckerContext &C) const; 464 465 void evalFsetpos(const FnDescription *Desc, const CallEvent &Call, 466 CheckerContext &C) const; 467 468 void evalFtell(const FnDescription *Desc, const CallEvent &Call, 469 CheckerContext &C) const; 470 471 void evalRewind(const FnDescription *Desc, const CallEvent &Call, 472 CheckerContext &C) const; 473 474 void preDefault(const FnDescription *Desc, const CallEvent &Call, 475 CheckerContext &C) const; 476 477 void evalClearerr(const FnDescription *Desc, const CallEvent &Call, 478 CheckerContext &C) const; 479 480 void evalFeofFerror(const FnDescription *Desc, const CallEvent &Call, 481 CheckerContext &C, 482 const StreamErrorState &ErrorKind) const; 483 484 void evalSetFeofFerror(const FnDescription *Desc, const CallEvent &Call, 485 CheckerContext &C, 486 const StreamErrorState &ErrorKind) const; 487 488 void preFflush(const FnDescription *Desc, const CallEvent &Call, 489 CheckerContext &C) const; 490 491 void evalFflush(const FnDescription *Desc, const CallEvent &Call, 492 CheckerContext &C) const; 493 494 void evalFileno(const FnDescription *Desc, const CallEvent &Call, 495 CheckerContext &C) const; 496 497 /// Check that the stream (in StreamVal) is not NULL. 498 /// If it can only be NULL a fatal error is emitted and nullptr returned. 499 /// Otherwise the return value is a new state where the stream is constrained 500 /// to be non-null. 501 ProgramStateRef ensureStreamNonNull(SVal StreamVal, const Expr *StreamE, 502 CheckerContext &C, 503 ProgramStateRef State) const; 504 505 /// Check that the stream is the opened state. 506 /// If the stream is known to be not opened an error is generated 507 /// and nullptr returned, otherwise the original state is returned. 508 ProgramStateRef ensureStreamOpened(SVal StreamVal, CheckerContext &C, 509 ProgramStateRef State) const; 510 511 /// Check that the stream has not an invalid ("indeterminate") file position, 512 /// generate warning for it. 513 /// (EOF is not an invalid position.) 514 /// The returned state can be nullptr if a fatal error was generated. 515 /// It can return non-null state if the stream has not an invalid position or 516 /// there is execution path with non-invalid position. 517 ProgramStateRef 518 ensureNoFilePositionIndeterminate(SVal StreamVal, CheckerContext &C, 519 ProgramStateRef State) const; 520 521 /// Check the legality of the 'whence' argument of 'fseek'. 522 /// Generate error and return nullptr if it is found to be illegal. 523 /// Otherwise returns the state. 524 /// (State is not changed here because the "whence" value is already known.) 525 ProgramStateRef ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 526 ProgramStateRef State) const; 527 528 /// Generate warning about stream in EOF state. 529 /// There will be always a state transition into the passed State, 530 /// by the new non-fatal error node or (if failed) a normal transition, 531 /// to ensure uniform handling. 532 void reportFEofWarning(SymbolRef StreamSym, CheckerContext &C, 533 ProgramStateRef State) const; 534 535 /// Emit resource leak warnings for the given symbols. 536 /// Createn a non-fatal error node for these, and returns it (if any warnings 537 /// were generated). Return value is non-null. 538 ExplodedNode *reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms, 539 CheckerContext &C, ExplodedNode *Pred) const; 540 541 /// Find the description data of the function called by a call event. 542 /// Returns nullptr if no function is recognized. 543 const FnDescription *lookupFn(const CallEvent &Call) const { 544 // Recognize "global C functions" with only integral or pointer arguments 545 // (and matching name) as stream functions. 546 if (!Call.isGlobalCFunction()) 547 return nullptr; 548 for (auto *P : Call.parameters()) { 549 QualType T = P->getType(); 550 if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) 551 return nullptr; 552 } 553 554 return FnDescriptions.lookup(Call); 555 } 556 557 /// Generate a message for BugReporterVisitor if the stored symbol is 558 /// marked as interesting by the actual bug report. 559 const NoteTag *constructNoteTag(CheckerContext &C, SymbolRef StreamSym, 560 const std::string &Message) const { 561 return C.getNoteTag([this, StreamSym, 562 Message](PathSensitiveBugReport &BR) -> std::string { 563 if (BR.isInteresting(StreamSym) && &BR.getBugType() == &BT_ResourceLeak) 564 return Message; 565 return ""; 566 }); 567 } 568 569 const NoteTag *constructSetEofNoteTag(CheckerContext &C, 570 SymbolRef StreamSym) const { 571 return C.getNoteTag([this, StreamSym](PathSensitiveBugReport &BR) { 572 if (!BR.isInteresting(StreamSym) || 573 &BR.getBugType() != this->getBT_StreamEof()) 574 return ""; 575 576 BR.markNotInteresting(StreamSym); 577 578 return "Assuming stream reaches end-of-file here"; 579 }); 580 } 581 582 void initMacroValues(CheckerContext &C) const { 583 if (EofVal) 584 return; 585 586 if (const std::optional<int> OptInt = 587 tryExpandAsInteger("EOF", C.getPreprocessor())) 588 EofVal = *OptInt; 589 else 590 EofVal = -1; 591 if (const std::optional<int> OptInt = 592 tryExpandAsInteger("SEEK_SET", C.getPreprocessor())) 593 SeekSetVal = *OptInt; 594 if (const std::optional<int> OptInt = 595 tryExpandAsInteger("SEEK_END", C.getPreprocessor())) 596 SeekEndVal = *OptInt; 597 if (const std::optional<int> OptInt = 598 tryExpandAsInteger("SEEK_CUR", C.getPreprocessor())) 599 SeekCurVal = *OptInt; 600 } 601 602 /// Searches for the ExplodedNode where the file descriptor was acquired for 603 /// StreamSym. 604 static const ExplodedNode *getAcquisitionSite(const ExplodedNode *N, 605 SymbolRef StreamSym, 606 CheckerContext &C); 607 }; 608 609 } // end anonymous namespace 610 611 const ExplodedNode *StreamChecker::getAcquisitionSite(const ExplodedNode *N, 612 SymbolRef StreamSym, 613 CheckerContext &C) { 614 ProgramStateRef State = N->getState(); 615 // When bug type is resource leak, exploded node N may not have state info 616 // for leaked file descriptor, but predecessor should have it. 617 if (!State->get<StreamMap>(StreamSym)) 618 N = N->getFirstPred(); 619 620 const ExplodedNode *Pred = N; 621 while (N) { 622 State = N->getState(); 623 if (!State->get<StreamMap>(StreamSym)) 624 return Pred; 625 Pred = N; 626 N = N->getFirstPred(); 627 } 628 629 return nullptr; 630 } 631 632 //===----------------------------------------------------------------------===// 633 // Methods of StreamChecker. 634 //===----------------------------------------------------------------------===// 635 636 void StreamChecker::checkPreCall(const CallEvent &Call, 637 CheckerContext &C) const { 638 initMacroValues(C); 639 640 const FnDescription *Desc = lookupFn(Call); 641 if (!Desc || !Desc->PreFn) 642 return; 643 644 Desc->PreFn(this, Desc, Call, C); 645 } 646 647 bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { 648 const FnDescription *Desc = lookupFn(Call); 649 if (!Desc && TestMode) 650 Desc = FnTestDescriptions.lookup(Call); 651 if (!Desc || !Desc->EvalFn) 652 return false; 653 654 Desc->EvalFn(this, Desc, Call, C); 655 656 return C.isDifferent(); 657 } 658 659 void StreamChecker::evalFopen(const FnDescription *Desc, const CallEvent &Call, 660 CheckerContext &C) const { 661 ProgramStateRef State = C.getState(); 662 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 663 if (!CE) 664 return; 665 666 DefinedSVal RetVal = makeRetVal(C, CE); 667 SymbolRef RetSym = RetVal.getAsSymbol(); 668 assert(RetSym && "RetVal must be a symbol here."); 669 670 State = State->BindExpr(CE, C.getLocationContext(), RetVal); 671 672 // Bifurcate the state into two: one with a valid FILE* pointer, the other 673 // with a NULL. 674 ProgramStateRef StateNotNull, StateNull; 675 std::tie(StateNotNull, StateNull) = 676 C.getConstraintManager().assumeDual(State, RetVal); 677 678 StateNotNull = 679 StateNotNull->set<StreamMap>(RetSym, StreamState::getOpened(Desc)); 680 StateNull = 681 StateNull->set<StreamMap>(RetSym, StreamState::getOpenFailed(Desc)); 682 683 C.addTransition(StateNotNull, 684 constructNoteTag(C, RetSym, "Stream opened here")); 685 C.addTransition(StateNull); 686 } 687 688 void StreamChecker::preFreopen(const FnDescription *Desc, const CallEvent &Call, 689 CheckerContext &C) const { 690 // Do not allow NULL as passed stream pointer but allow a closed stream. 691 ProgramStateRef State = C.getState(); 692 State = ensureStreamNonNull(getStreamArg(Desc, Call), 693 Call.getArgExpr(Desc->StreamArgNo), C, State); 694 if (!State) 695 return; 696 697 C.addTransition(State); 698 } 699 700 void StreamChecker::evalFreopen(const FnDescription *Desc, 701 const CallEvent &Call, 702 CheckerContext &C) const { 703 ProgramStateRef State = C.getState(); 704 705 auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 706 if (!CE) 707 return; 708 709 std::optional<DefinedSVal> StreamVal = 710 getStreamArg(Desc, Call).getAs<DefinedSVal>(); 711 if (!StreamVal) 712 return; 713 714 SymbolRef StreamSym = StreamVal->getAsSymbol(); 715 // Do not care about concrete values for stream ("(FILE *)0x12345"?). 716 // FIXME: Can be stdin, stdout, stderr such values? 717 if (!StreamSym) 718 return; 719 720 // Do not handle untracked stream. It is probably escaped. 721 if (!State->get<StreamMap>(StreamSym)) 722 return; 723 724 // Generate state for non-failed case. 725 // Return value is the passed stream pointer. 726 // According to the documentations, the stream is closed first 727 // but any close error is ignored. The state changes to (or remains) opened. 728 ProgramStateRef StateRetNotNull = 729 State->BindExpr(CE, C.getLocationContext(), *StreamVal); 730 // Generate state for NULL return value. 731 // Stream switches to OpenFailed state. 732 ProgramStateRef StateRetNull = 733 State->BindExpr(CE, C.getLocationContext(), 734 C.getSValBuilder().makeNullWithType(CE->getType())); 735 736 StateRetNotNull = 737 StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened(Desc)); 738 StateRetNull = 739 StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed(Desc)); 740 741 C.addTransition(StateRetNotNull, 742 constructNoteTag(C, StreamSym, "Stream reopened here")); 743 C.addTransition(StateRetNull); 744 } 745 746 void StreamChecker::evalFclose(const FnDescription *Desc, const CallEvent &Call, 747 CheckerContext &C) const { 748 ProgramStateRef State = C.getState(); 749 StreamOperationEvaluator E(C); 750 if (!E.Init(Desc, Call, C, State)) 751 return; 752 753 // Close the File Descriptor. 754 // Regardless if the close fails or not, stream becomes "closed" 755 // and can not be used any more. 756 State = E.setStreamState(State, StreamState::getClosed(Desc)); 757 758 // Return 0 on success, EOF on failure. 759 C.addTransition(E.bindReturnValue(State, C, 0)); 760 C.addTransition(E.bindReturnValue(State, C, *EofVal)); 761 } 762 763 void StreamChecker::preReadWrite(const FnDescription *Desc, 764 const CallEvent &Call, CheckerContext &C, 765 bool IsRead) const { 766 ProgramStateRef State = C.getState(); 767 SVal StreamVal = getStreamArg(Desc, Call); 768 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C, 769 State); 770 if (!State) 771 return; 772 State = ensureStreamOpened(StreamVal, C, State); 773 if (!State) 774 return; 775 State = ensureNoFilePositionIndeterminate(StreamVal, C, State); 776 if (!State) 777 return; 778 779 if (!IsRead) { 780 C.addTransition(State); 781 return; 782 } 783 784 SymbolRef Sym = StreamVal.getAsSymbol(); 785 if (Sym && State->get<StreamMap>(Sym)) { 786 const StreamState *SS = State->get<StreamMap>(Sym); 787 if (SS->ErrorState & ErrorFEof) 788 reportFEofWarning(Sym, C, State); 789 } else { 790 C.addTransition(State); 791 } 792 } 793 794 void StreamChecker::evalFreadFwrite(const FnDescription *Desc, 795 const CallEvent &Call, CheckerContext &C, 796 bool IsFread) const { 797 ProgramStateRef State = C.getState(); 798 StreamOperationEvaluator E(C); 799 if (!E.Init(Desc, Call, C, State)) 800 return; 801 802 std::optional<NonLoc> SizeVal = Call.getArgSVal(1).getAs<NonLoc>(); 803 if (!SizeVal) 804 return; 805 std::optional<NonLoc> NMembVal = Call.getArgSVal(2).getAs<NonLoc>(); 806 if (!NMembVal) 807 return; 808 809 // C'99 standard, §7.19.8.1.3, the return value of fread: 810 // The fread function returns the number of elements successfully read, which 811 // may be less than nmemb if a read error or end-of-file is encountered. If 812 // size or nmemb is zero, fread returns zero and the contents of the array and 813 // the state of the stream remain unchanged. 814 if (State->isNull(*SizeVal).isConstrainedTrue() || 815 State->isNull(*NMembVal).isConstrainedTrue()) { 816 // This is the "size or nmemb is zero" case. 817 // Just return 0, do nothing more (not clear the error flags). 818 C.addTransition(E.bindReturnValue(State, C, 0)); 819 return; 820 } 821 822 // Generate a transition for the success state. 823 // If we know the state to be FEOF at fread, do not add a success state. 824 if (!IsFread || !E.isStreamEof()) { 825 ProgramStateRef StateNotFailed = 826 State->BindExpr(E.CE, C.getLocationContext(), *NMembVal); 827 StateNotFailed = 828 E.setStreamState(StateNotFailed, StreamState::getOpened(Desc)); 829 C.addTransition(StateNotFailed); 830 } 831 832 // Add transition for the failed state. 833 NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>(); 834 ProgramStateRef StateFailed = 835 State->BindExpr(E.CE, C.getLocationContext(), RetVal); 836 StateFailed = E.assumeBinOpNN(StateFailed, BO_LT, RetVal, *NMembVal); 837 if (!StateFailed) 838 return; 839 840 StreamErrorState NewES; 841 if (IsFread) 842 NewES = E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError; 843 else 844 NewES = ErrorFError; 845 // If a (non-EOF) error occurs, the resulting value of the file position 846 // indicator for the stream is indeterminate. 847 StateFailed = E.setStreamState( 848 StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof())); 849 if (IsFread && !E.isStreamEof()) 850 C.addTransition(StateFailed, constructSetEofNoteTag(C, E.StreamSym)); 851 else 852 C.addTransition(StateFailed); 853 } 854 855 void StreamChecker::evalFgetx(const FnDescription *Desc, const CallEvent &Call, 856 CheckerContext &C, bool SingleChar) const { 857 // `fgetc` returns the read character on success, otherwise returns EOF. 858 // `fgets` returns the read buffer address on success, otherwise returns NULL. 859 860 ProgramStateRef State = C.getState(); 861 StreamOperationEvaluator E(C); 862 if (!E.Init(Desc, Call, C, State)) 863 return; 864 865 if (!E.isStreamEof()) { 866 if (SingleChar) { 867 // Generate a transition for the success state of `fgetc`. 868 NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>(); 869 ProgramStateRef StateNotFailed = 870 State->BindExpr(E.CE, C.getLocationContext(), RetVal); 871 // The returned 'unsigned char' of `fgetc` is converted to 'int', 872 // so we need to check if it is in range [0, 255]. 873 StateNotFailed = StateNotFailed->assumeInclusiveRange( 874 RetVal, 875 E.SVB.getBasicValueFactory().getValue(0, E.ACtx.UnsignedCharTy), 876 E.SVB.getBasicValueFactory().getMaxValue(E.ACtx.UnsignedCharTy), 877 true); 878 if (!StateNotFailed) 879 return; 880 C.addTransition(StateNotFailed); 881 } else { 882 // Generate a transition for the success state of `fgets`. 883 std::optional<DefinedSVal> GetBuf = 884 Call.getArgSVal(0).getAs<DefinedSVal>(); 885 if (!GetBuf) 886 return; 887 ProgramStateRef StateNotFailed = 888 State->BindExpr(E.CE, C.getLocationContext(), *GetBuf); 889 StateNotFailed = 890 E.setStreamState(StateNotFailed, StreamState::getOpened(Desc)); 891 C.addTransition(StateNotFailed); 892 } 893 } 894 895 // Add transition for the failed state. 896 ProgramStateRef StateFailed; 897 if (SingleChar) 898 StateFailed = E.bindReturnValue(State, C, *EofVal); 899 else 900 StateFailed = E.bindNullReturnValue(State, C); 901 902 // If a (non-EOF) error occurs, the resulting value of the file position 903 // indicator for the stream is indeterminate. 904 StreamErrorState NewES = 905 E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError; 906 StateFailed = E.setStreamState( 907 StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof())); 908 if (!E.isStreamEof()) 909 C.addTransition(StateFailed, constructSetEofNoteTag(C, E.StreamSym)); 910 else 911 C.addTransition(StateFailed); 912 } 913 914 void StreamChecker::evalFputx(const FnDescription *Desc, const CallEvent &Call, 915 CheckerContext &C, bool IsSingleChar) const { 916 // `fputc` returns the written character on success, otherwise returns EOF. 917 // `fputs` returns a nonnegative value on success, otherwise returns EOF. 918 919 ProgramStateRef State = C.getState(); 920 StreamOperationEvaluator E(C); 921 if (!E.Init(Desc, Call, C, State)) 922 return; 923 924 if (IsSingleChar) { 925 // Generate a transition for the success state of `fputc`. 926 std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>(); 927 if (!PutVal) 928 return; 929 ProgramStateRef StateNotFailed = 930 State->BindExpr(E.CE, C.getLocationContext(), *PutVal); 931 StateNotFailed = 932 E.setStreamState(StateNotFailed, StreamState::getOpened(Desc)); 933 C.addTransition(StateNotFailed); 934 } else { 935 // Generate a transition for the success state of `fputs`. 936 NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>(); 937 ProgramStateRef StateNotFailed = 938 State->BindExpr(E.CE, C.getLocationContext(), RetVal); 939 StateNotFailed = 940 E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call)); 941 if (!StateNotFailed) 942 return; 943 StateNotFailed = 944 E.setStreamState(StateNotFailed, StreamState::getOpened(Desc)); 945 C.addTransition(StateNotFailed); 946 } 947 948 // Add transition for the failed state. The resulting value of the file 949 // position indicator for the stream is indeterminate. 950 ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal); 951 StateFailed = E.setStreamState( 952 StateFailed, StreamState::getOpened(Desc, ErrorFError, true)); 953 C.addTransition(StateFailed); 954 } 955 956 void StreamChecker::evalFprintf(const FnDescription *Desc, 957 const CallEvent &Call, 958 CheckerContext &C) const { 959 if (Call.getNumArgs() < 2) 960 return; 961 962 ProgramStateRef State = C.getState(); 963 StreamOperationEvaluator E(C); 964 if (!E.Init(Desc, Call, C, State)) 965 return; 966 967 NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>(); 968 State = State->BindExpr(E.CE, C.getLocationContext(), RetVal); 969 auto Cond = 970 E.SVB 971 .evalBinOp(State, BO_GE, RetVal, E.SVB.makeZeroVal(E.ACtx.IntTy), 972 E.SVB.getConditionType()) 973 .getAs<DefinedOrUnknownSVal>(); 974 if (!Cond) 975 return; 976 ProgramStateRef StateNotFailed, StateFailed; 977 std::tie(StateNotFailed, StateFailed) = State->assume(*Cond); 978 979 StateNotFailed = 980 E.setStreamState(StateNotFailed, StreamState::getOpened(Desc)); 981 C.addTransition(StateNotFailed); 982 983 // Add transition for the failed state. The resulting value of the file 984 // position indicator for the stream is indeterminate. 985 StateFailed = E.setStreamState( 986 StateFailed, StreamState::getOpened(Desc, ErrorFError, true)); 987 C.addTransition(StateFailed); 988 } 989 990 void StreamChecker::evalFscanf(const FnDescription *Desc, const CallEvent &Call, 991 CheckerContext &C) const { 992 if (Call.getNumArgs() < 2) 993 return; 994 995 ProgramStateRef State = C.getState(); 996 StreamOperationEvaluator E(C); 997 if (!E.Init(Desc, Call, C, State)) 998 return; 999 1000 // Add the success state. 1001 // In this context "success" means there is not an EOF or other read error 1002 // before any item is matched in 'fscanf'. But there may be match failure, 1003 // therefore return value can be 0 or greater. 1004 // It is not specified what happens if some items (not all) are matched and 1005 // then EOF or read error happens. Now this case is handled like a "success" 1006 // case, and no error flags are set on the stream. This is probably not 1007 // accurate, and the POSIX documentation does not tell more. 1008 if (!E.isStreamEof()) { 1009 NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>(); 1010 ProgramStateRef StateNotFailed = 1011 State->BindExpr(E.CE, C.getLocationContext(), RetVal); 1012 StateNotFailed = 1013 E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call)); 1014 if (StateNotFailed) 1015 C.addTransition(StateNotFailed); 1016 } 1017 1018 // Add transition for the failed state. 1019 // Error occurs if nothing is matched yet and reading the input fails. 1020 // Error can be EOF, or other error. At "other error" FERROR or 'errno' can 1021 // be set but it is not further specified if all are required to be set. 1022 // Documentation does not mention, but file position will be set to 1023 // indeterminate similarly as at 'fread'. 1024 ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal); 1025 StreamErrorState NewES = 1026 E.isStreamEof() ? ErrorFEof : ErrorNone | ErrorFEof | ErrorFError; 1027 StateFailed = E.setStreamState( 1028 StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof())); 1029 if (!E.isStreamEof()) 1030 C.addTransition(StateFailed, constructSetEofNoteTag(C, E.StreamSym)); 1031 else 1032 C.addTransition(StateFailed); 1033 } 1034 1035 void StreamChecker::evalUngetc(const FnDescription *Desc, const CallEvent &Call, 1036 CheckerContext &C) const { 1037 ProgramStateRef State = C.getState(); 1038 StreamOperationEvaluator E(C); 1039 if (!E.Init(Desc, Call, C, State)) 1040 return; 1041 1042 // Generate a transition for the success state. 1043 std::optional<NonLoc> PutVal = Call.getArgSVal(0).getAs<NonLoc>(); 1044 if (!PutVal) 1045 return; 1046 ProgramStateRef StateNotFailed = E.bindReturnValue(State, C, *PutVal); 1047 StateNotFailed = 1048 E.setStreamState(StateNotFailed, StreamState::getOpened(Desc)); 1049 C.addTransition(StateNotFailed); 1050 1051 // Add transition for the failed state. 1052 // Failure of 'ungetc' does not result in feof or ferror state. 1053 // If the PutVal has value of EofVal the function should "fail", but this is 1054 // the same transition as the success state. 1055 // In this case only one state transition is added by the analyzer (the two 1056 // new states may be similar). 1057 ProgramStateRef StateFailed = E.bindReturnValue(State, C, *EofVal); 1058 StateFailed = E.setStreamState(StateFailed, StreamState::getOpened(Desc)); 1059 C.addTransition(StateFailed); 1060 } 1061 1062 void StreamChecker::evalGetdelim(const FnDescription *Desc, 1063 const CallEvent &Call, 1064 CheckerContext &C) const { 1065 ProgramStateRef State = C.getState(); 1066 StreamOperationEvaluator E(C); 1067 if (!E.Init(Desc, Call, C, State)) 1068 return; 1069 1070 // Upon successful completion, the getline() and getdelim() functions shall 1071 // return the number of bytes written into the buffer. 1072 // If the end-of-file indicator for the stream is set, the function shall 1073 // return -1. 1074 // If an error occurs, the function shall return -1 and set 'errno'. 1075 1076 // Add transition for the successful state. 1077 if (!E.isStreamEof()) { 1078 NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>(); 1079 ProgramStateRef StateNotFailed = 1080 State->BindExpr(E.CE, C.getLocationContext(), RetVal); 1081 StateNotFailed = 1082 E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call)); 1083 if (!StateNotFailed) 1084 return; 1085 C.addTransition(StateNotFailed); 1086 } 1087 1088 // Add transition for the failed state. 1089 // If a (non-EOF) error occurs, the resulting value of the file position 1090 // indicator for the stream is indeterminate. 1091 ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1); 1092 StreamErrorState NewES = 1093 E.isStreamEof() ? ErrorFEof : ErrorFEof | ErrorFError; 1094 StateFailed = E.setStreamState( 1095 StateFailed, StreamState::getOpened(Desc, NewES, !NewES.isFEof())); 1096 if (E.isStreamEof()) 1097 C.addTransition(StateFailed, constructSetEofNoteTag(C, E.StreamSym)); 1098 else 1099 C.addTransition(StateFailed); 1100 } 1101 1102 void StreamChecker::preFseek(const FnDescription *Desc, const CallEvent &Call, 1103 CheckerContext &C) const { 1104 ProgramStateRef State = C.getState(); 1105 SVal StreamVal = getStreamArg(Desc, Call); 1106 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C, 1107 State); 1108 if (!State) 1109 return; 1110 State = ensureStreamOpened(StreamVal, C, State); 1111 if (!State) 1112 return; 1113 State = ensureFseekWhenceCorrect(Call.getArgSVal(2), C, State); 1114 if (!State) 1115 return; 1116 1117 C.addTransition(State); 1118 } 1119 1120 void StreamChecker::evalFseek(const FnDescription *Desc, const CallEvent &Call, 1121 CheckerContext &C) const { 1122 ProgramStateRef State = C.getState(); 1123 StreamOperationEvaluator E(C); 1124 if (!E.Init(Desc, Call, C, State)) 1125 return; 1126 1127 const llvm::APSInt *PosV = 1128 C.getSValBuilder().getKnownValue(State, Call.getArgSVal(1)); 1129 const llvm::APSInt *WhenceV = 1130 C.getSValBuilder().getKnownValue(State, Call.getArgSVal(2)); 1131 1132 // Bifurcate the state into failed and non-failed. 1133 // Return zero on success, nonzero on error. 1134 ProgramStateRef StateNotFailed, StateFailed; 1135 std::tie(StateFailed, StateNotFailed) = E.makeRetValAndAssumeDual(State, C); 1136 1137 // No failure: Reset the state to opened with no error. 1138 StateNotFailed = 1139 E.setStreamState(StateNotFailed, StreamState::getOpened(Desc)); 1140 C.addTransition(StateNotFailed); 1141 1142 // At error it is possible that fseek fails but sets none of the error flags. 1143 // If fseek failed, assume that the file position becomes indeterminate in any 1144 // case. 1145 StreamErrorState NewErrS = ErrorNone | ErrorFError; 1146 // Setting the position to start of file never produces EOF error. 1147 if (!(PosV && *PosV == 0 && WhenceV && *WhenceV == SeekSetVal)) 1148 NewErrS = NewErrS | ErrorFEof; 1149 StateFailed = E.setStreamState(StateFailed, 1150 StreamState::getOpened(Desc, NewErrS, true)); 1151 C.addTransition(StateFailed, constructSetEofNoteTag(C, E.StreamSym)); 1152 } 1153 1154 void StreamChecker::evalFgetpos(const FnDescription *Desc, 1155 const CallEvent &Call, 1156 CheckerContext &C) const { 1157 ProgramStateRef State = C.getState(); 1158 StreamOperationEvaluator E(C); 1159 if (!E.Init(Desc, Call, C, State)) 1160 return; 1161 1162 ProgramStateRef StateNotFailed, StateFailed; 1163 std::tie(StateFailed, StateNotFailed) = E.makeRetValAndAssumeDual(State, C); 1164 1165 // This function does not affect the stream state. 1166 // Still we add success and failure state with the appropriate return value. 1167 // StdLibraryFunctionsChecker can change these states (set the 'errno' state). 1168 C.addTransition(StateNotFailed); 1169 C.addTransition(StateFailed); 1170 } 1171 1172 void StreamChecker::evalFsetpos(const FnDescription *Desc, 1173 const CallEvent &Call, 1174 CheckerContext &C) const { 1175 ProgramStateRef State = C.getState(); 1176 StreamOperationEvaluator E(C); 1177 if (!E.Init(Desc, Call, C, State)) 1178 return; 1179 1180 ProgramStateRef StateNotFailed, StateFailed; 1181 std::tie(StateFailed, StateNotFailed) = E.makeRetValAndAssumeDual(State, C); 1182 1183 StateNotFailed = E.setStreamState( 1184 StateNotFailed, StreamState::getOpened(Desc, ErrorNone, false)); 1185 1186 // At failure ferror could be set. 1187 // The standards do not tell what happens with the file position at failure. 1188 // But we can assume that it is dangerous to make a next I/O operation after 1189 // the position was not set correctly (similar to 'fseek'). 1190 StateFailed = E.setStreamState( 1191 StateFailed, StreamState::getOpened(Desc, ErrorNone | ErrorFError, true)); 1192 1193 C.addTransition(StateNotFailed); 1194 C.addTransition(StateFailed); 1195 } 1196 1197 void StreamChecker::evalFtell(const FnDescription *Desc, const CallEvent &Call, 1198 CheckerContext &C) const { 1199 ProgramStateRef State = C.getState(); 1200 StreamOperationEvaluator E(C); 1201 if (!E.Init(Desc, Call, C, State)) 1202 return; 1203 1204 NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>(); 1205 ProgramStateRef StateNotFailed = 1206 State->BindExpr(E.CE, C.getLocationContext(), RetVal); 1207 StateNotFailed = 1208 E.assumeBinOpNN(StateNotFailed, BO_GE, RetVal, E.getZeroVal(Call)); 1209 if (!StateNotFailed) 1210 return; 1211 1212 ProgramStateRef StateFailed = E.bindReturnValue(State, C, -1); 1213 1214 // This function does not affect the stream state. 1215 // Still we add success and failure state with the appropriate return value. 1216 // StdLibraryFunctionsChecker can change these states (set the 'errno' state). 1217 C.addTransition(StateNotFailed); 1218 C.addTransition(StateFailed); 1219 } 1220 1221 void StreamChecker::evalRewind(const FnDescription *Desc, const CallEvent &Call, 1222 CheckerContext &C) const { 1223 ProgramStateRef State = C.getState(); 1224 StreamOperationEvaluator E(C); 1225 if (!E.Init(Desc, Call, C, State)) 1226 return; 1227 1228 State = 1229 E.setStreamState(State, StreamState::getOpened(Desc, ErrorNone, false)); 1230 C.addTransition(State); 1231 } 1232 1233 void StreamChecker::preFflush(const FnDescription *Desc, const CallEvent &Call, 1234 CheckerContext &C) const { 1235 ProgramStateRef State = C.getState(); 1236 SVal StreamVal = getStreamArg(Desc, Call); 1237 std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>(); 1238 if (!Stream) 1239 return; 1240 1241 ProgramStateRef StateNotNull, StateNull; 1242 std::tie(StateNotNull, StateNull) = 1243 C.getConstraintManager().assumeDual(State, *Stream); 1244 if (StateNotNull && !StateNull) 1245 ensureStreamOpened(StreamVal, C, StateNotNull); 1246 } 1247 1248 void StreamChecker::evalFflush(const FnDescription *Desc, const CallEvent &Call, 1249 CheckerContext &C) const { 1250 ProgramStateRef State = C.getState(); 1251 SVal StreamVal = getStreamArg(Desc, Call); 1252 std::optional<DefinedSVal> Stream = StreamVal.getAs<DefinedSVal>(); 1253 if (!Stream) 1254 return; 1255 1256 // Skip if the stream can be both NULL and non-NULL. 1257 ProgramStateRef StateNotNull, StateNull; 1258 std::tie(StateNotNull, StateNull) = 1259 C.getConstraintManager().assumeDual(State, *Stream); 1260 if (StateNotNull && StateNull) 1261 return; 1262 if (StateNotNull && !StateNull) 1263 State = StateNotNull; 1264 else 1265 State = StateNull; 1266 1267 const CallExpr *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); 1268 if (!CE) 1269 return; 1270 1271 // `fflush` returns EOF on failure, otherwise returns 0. 1272 ProgramStateRef StateFailed = bindInt(*EofVal, State, C, CE); 1273 ProgramStateRef StateNotFailed = bindInt(0, State, C, CE); 1274 1275 // Clear error states if `fflush` returns 0, but retain their EOF flags. 1276 auto ClearErrorInNotFailed = [&StateNotFailed, Desc](SymbolRef Sym, 1277 const StreamState *SS) { 1278 if (SS->ErrorState & ErrorFError) { 1279 StreamErrorState NewES = 1280 (SS->ErrorState & ErrorFEof) ? ErrorFEof : ErrorNone; 1281 StreamState NewSS = StreamState::getOpened(Desc, NewES, false); 1282 StateNotFailed = StateNotFailed->set<StreamMap>(Sym, NewSS); 1283 } 1284 }; 1285 1286 if (StateNotNull && !StateNull) { 1287 // Skip if the input stream's state is unknown, open-failed or closed. 1288 if (SymbolRef StreamSym = StreamVal.getAsSymbol()) { 1289 const StreamState *SS = State->get<StreamMap>(StreamSym); 1290 if (SS) { 1291 assert(SS->isOpened() && "Stream is expected to be opened"); 1292 ClearErrorInNotFailed(StreamSym, SS); 1293 } else 1294 return; 1295 } 1296 } else { 1297 // Clear error states for all streams. 1298 const StreamMapTy &Map = StateNotFailed->get<StreamMap>(); 1299 for (const auto &I : Map) { 1300 SymbolRef Sym = I.first; 1301 const StreamState &SS = I.second; 1302 if (SS.isOpened()) 1303 ClearErrorInNotFailed(Sym, &SS); 1304 } 1305 } 1306 1307 C.addTransition(StateNotFailed); 1308 C.addTransition(StateFailed); 1309 } 1310 1311 void StreamChecker::evalClearerr(const FnDescription *Desc, 1312 const CallEvent &Call, 1313 CheckerContext &C) const { 1314 ProgramStateRef State = C.getState(); 1315 StreamOperationEvaluator E(C); 1316 if (!E.Init(Desc, Call, C, State)) 1317 return; 1318 1319 // FilePositionIndeterminate is not cleared. 1320 State = E.setStreamState( 1321 State, 1322 StreamState::getOpened(Desc, ErrorNone, E.SS->FilePositionIndeterminate)); 1323 C.addTransition(State); 1324 } 1325 1326 void StreamChecker::evalFeofFerror(const FnDescription *Desc, 1327 const CallEvent &Call, CheckerContext &C, 1328 const StreamErrorState &ErrorKind) const { 1329 ProgramStateRef State = C.getState(); 1330 StreamOperationEvaluator E(C); 1331 if (!E.Init(Desc, Call, C, State)) 1332 return; 1333 1334 if (E.SS->ErrorState & ErrorKind) { 1335 // Execution path with error of ErrorKind. 1336 // Function returns true. 1337 // From now on it is the only one error state. 1338 ProgramStateRef TrueState = bindAndAssumeTrue(State, C, E.CE); 1339 C.addTransition(E.setStreamState( 1340 TrueState, StreamState::getOpened(Desc, ErrorKind, 1341 E.SS->FilePositionIndeterminate && 1342 !ErrorKind.isFEof()))); 1343 } 1344 if (StreamErrorState NewES = E.SS->ErrorState & (~ErrorKind)) { 1345 // Execution path(s) with ErrorKind not set. 1346 // Function returns false. 1347 // New error state is everything before minus ErrorKind. 1348 ProgramStateRef FalseState = E.bindReturnValue(State, C, 0); 1349 C.addTransition(E.setStreamState( 1350 FalseState, 1351 StreamState::getOpened( 1352 Desc, NewES, E.SS->FilePositionIndeterminate && !NewES.isFEof()))); 1353 } 1354 } 1355 1356 void StreamChecker::evalFileno(const FnDescription *Desc, const CallEvent &Call, 1357 CheckerContext &C) const { 1358 // Fileno should fail only if the passed pointer is invalid. 1359 // Some of the preconditions are checked already in preDefault. 1360 // Here we can assume that the operation does not fail, because if we 1361 // introduced a separate branch where fileno() returns -1, then it would cause 1362 // many unexpected and unwanted warnings in situations where fileno() is 1363 // called on valid streams. 1364 // The stream error states are not modified by 'fileno', and 'errno' is also 1365 // left unchanged (so this evalCall does not invalidate it, but we have a 1366 // custom evalCall instead of the default that would invalidate it). 1367 ProgramStateRef State = C.getState(); 1368 StreamOperationEvaluator E(C); 1369 if (!E.Init(Desc, Call, C, State)) 1370 return; 1371 1372 NonLoc RetVal = makeRetVal(C, E.CE).castAs<NonLoc>(); 1373 State = State->BindExpr(E.CE, C.getLocationContext(), RetVal); 1374 State = E.assumeBinOpNN(State, BO_GE, RetVal, E.getZeroVal(Call)); 1375 if (!State) 1376 return; 1377 1378 C.addTransition(State); 1379 } 1380 1381 void StreamChecker::preDefault(const FnDescription *Desc, const CallEvent &Call, 1382 CheckerContext &C) const { 1383 ProgramStateRef State = C.getState(); 1384 SVal StreamVal = getStreamArg(Desc, Call); 1385 State = ensureStreamNonNull(StreamVal, Call.getArgExpr(Desc->StreamArgNo), C, 1386 State); 1387 if (!State) 1388 return; 1389 State = ensureStreamOpened(StreamVal, C, State); 1390 if (!State) 1391 return; 1392 1393 C.addTransition(State); 1394 } 1395 1396 void StreamChecker::evalSetFeofFerror(const FnDescription *Desc, 1397 const CallEvent &Call, CheckerContext &C, 1398 const StreamErrorState &ErrorKind) const { 1399 ProgramStateRef State = C.getState(); 1400 SymbolRef StreamSym = getStreamArg(Desc, Call).getAsSymbol(); 1401 assert(StreamSym && "Operation not permitted on non-symbolic stream value."); 1402 const StreamState *SS = State->get<StreamMap>(StreamSym); 1403 assert(SS && "Stream should be tracked by the checker."); 1404 State = State->set<StreamMap>( 1405 StreamSym, StreamState::getOpened(SS->LastOperation, ErrorKind)); 1406 C.addTransition(State); 1407 } 1408 1409 ProgramStateRef 1410 StreamChecker::ensureStreamNonNull(SVal StreamVal, const Expr *StreamE, 1411 CheckerContext &C, 1412 ProgramStateRef State) const { 1413 auto Stream = StreamVal.getAs<DefinedSVal>(); 1414 if (!Stream) 1415 return State; 1416 1417 ConstraintManager &CM = C.getConstraintManager(); 1418 1419 ProgramStateRef StateNotNull, StateNull; 1420 std::tie(StateNotNull, StateNull) = CM.assumeDual(State, *Stream); 1421 1422 if (!StateNotNull && StateNull) { 1423 if (ExplodedNode *N = C.generateErrorNode(StateNull)) { 1424 auto R = std::make_unique<PathSensitiveBugReport>( 1425 BT_FileNull, "Stream pointer might be NULL.", N); 1426 if (StreamE) 1427 bugreporter::trackExpressionValue(N, StreamE, *R); 1428 C.emitReport(std::move(R)); 1429 } 1430 return nullptr; 1431 } 1432 1433 return StateNotNull; 1434 } 1435 1436 ProgramStateRef StreamChecker::ensureStreamOpened(SVal StreamVal, 1437 CheckerContext &C, 1438 ProgramStateRef State) const { 1439 SymbolRef Sym = StreamVal.getAsSymbol(); 1440 if (!Sym) 1441 return State; 1442 1443 const StreamState *SS = State->get<StreamMap>(Sym); 1444 if (!SS) 1445 return State; 1446 1447 if (SS->isClosed()) { 1448 // Using a stream pointer after 'fclose' causes undefined behavior 1449 // according to cppreference.com . 1450 ExplodedNode *N = C.generateErrorNode(); 1451 if (N) { 1452 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1453 BT_UseAfterClose, 1454 "Stream might be already closed. Causes undefined behaviour.", N)); 1455 return nullptr; 1456 } 1457 1458 return State; 1459 } 1460 1461 if (SS->isOpenFailed()) { 1462 // Using a stream that has failed to open is likely to cause problems. 1463 // This should usually not occur because stream pointer is NULL. 1464 // But freopen can cause a state when stream pointer remains non-null but 1465 // failed to open. 1466 ExplodedNode *N = C.generateErrorNode(); 1467 if (N) { 1468 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1469 BT_UseAfterOpenFailed, 1470 "Stream might be invalid after " 1471 "(re-)opening it has failed. " 1472 "Can cause undefined behaviour.", 1473 N)); 1474 return nullptr; 1475 } 1476 } 1477 1478 return State; 1479 } 1480 1481 ProgramStateRef StreamChecker::ensureNoFilePositionIndeterminate( 1482 SVal StreamVal, CheckerContext &C, ProgramStateRef State) const { 1483 static const char *BugMessage = 1484 "File position of the stream might be 'indeterminate' " 1485 "after a failed operation. " 1486 "Can cause undefined behavior."; 1487 1488 SymbolRef Sym = StreamVal.getAsSymbol(); 1489 if (!Sym) 1490 return State; 1491 1492 const StreamState *SS = State->get<StreamMap>(Sym); 1493 if (!SS) 1494 return State; 1495 1496 assert(SS->isOpened() && "First ensure that stream is opened."); 1497 1498 if (SS->FilePositionIndeterminate) { 1499 if (SS->ErrorState & ErrorFEof) { 1500 // The error is unknown but may be FEOF. 1501 // Continue analysis with the FEOF error state. 1502 // Report warning because the other possible error states. 1503 ExplodedNode *N = C.generateNonFatalErrorNode(State); 1504 if (!N) 1505 return nullptr; 1506 1507 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1508 BT_IndeterminatePosition, BugMessage, N)); 1509 return State->set<StreamMap>( 1510 Sym, StreamState::getOpened(SS->LastOperation, ErrorFEof, false)); 1511 } 1512 1513 // Known or unknown error state without FEOF possible. 1514 // Stop analysis, report error. 1515 ExplodedNode *N = C.generateErrorNode(State); 1516 if (N) 1517 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1518 BT_IndeterminatePosition, BugMessage, N)); 1519 1520 return nullptr; 1521 } 1522 1523 return State; 1524 } 1525 1526 ProgramStateRef 1527 StreamChecker::ensureFseekWhenceCorrect(SVal WhenceVal, CheckerContext &C, 1528 ProgramStateRef State) const { 1529 std::optional<nonloc::ConcreteInt> CI = 1530 WhenceVal.getAs<nonloc::ConcreteInt>(); 1531 if (!CI) 1532 return State; 1533 1534 int64_t X = CI->getValue().getSExtValue(); 1535 if (X == SeekSetVal || X == SeekCurVal || X == SeekEndVal) 1536 return State; 1537 1538 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 1539 C.emitReport(std::make_unique<PathSensitiveBugReport>( 1540 BT_IllegalWhence, 1541 "The whence argument to fseek() should be " 1542 "SEEK_SET, SEEK_END, or SEEK_CUR.", 1543 N)); 1544 return nullptr; 1545 } 1546 1547 return State; 1548 } 1549 1550 void StreamChecker::reportFEofWarning(SymbolRef StreamSym, CheckerContext &C, 1551 ProgramStateRef State) const { 1552 if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { 1553 auto R = std::make_unique<PathSensitiveBugReport>( 1554 BT_StreamEof, 1555 "Read function called when stream is in EOF state. " 1556 "Function has no effect.", 1557 N); 1558 R->markInteresting(StreamSym); 1559 C.emitReport(std::move(R)); 1560 return; 1561 } 1562 C.addTransition(State); 1563 } 1564 1565 ExplodedNode * 1566 StreamChecker::reportLeaks(const SmallVector<SymbolRef, 2> &LeakedSyms, 1567 CheckerContext &C, ExplodedNode *Pred) const { 1568 ExplodedNode *Err = C.generateNonFatalErrorNode(C.getState(), Pred); 1569 if (!Err) 1570 return Pred; 1571 1572 for (SymbolRef LeakSym : LeakedSyms) { 1573 // Resource leaks can result in multiple warning that describe the same kind 1574 // of programming error: 1575 // void f() { 1576 // FILE *F = fopen("a.txt"); 1577 // if (rand()) // state split 1578 // return; // warning 1579 // } // warning 1580 // While this isn't necessarily true (leaking the same stream could result 1581 // from a different kinds of errors), the reduction in redundant reports 1582 // makes this a worthwhile heuristic. 1583 // FIXME: Add a checker option to turn this uniqueing feature off. 1584 const ExplodedNode *StreamOpenNode = getAcquisitionSite(Err, LeakSym, C); 1585 assert(StreamOpenNode && "Could not find place of stream opening."); 1586 1587 PathDiagnosticLocation LocUsedForUniqueing; 1588 if (const Stmt *StreamStmt = StreamOpenNode->getStmtForDiagnostics()) 1589 LocUsedForUniqueing = PathDiagnosticLocation::createBegin( 1590 StreamStmt, C.getSourceManager(), 1591 StreamOpenNode->getLocationContext()); 1592 1593 std::unique_ptr<PathSensitiveBugReport> R = 1594 std::make_unique<PathSensitiveBugReport>( 1595 BT_ResourceLeak, 1596 "Opened stream never closed. Potential resource leak.", Err, 1597 LocUsedForUniqueing, 1598 StreamOpenNode->getLocationContext()->getDecl()); 1599 R->markInteresting(LeakSym); 1600 C.emitReport(std::move(R)); 1601 } 1602 1603 return Err; 1604 } 1605 1606 void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, 1607 CheckerContext &C) const { 1608 ProgramStateRef State = C.getState(); 1609 1610 llvm::SmallVector<SymbolRef, 2> LeakedSyms; 1611 1612 const StreamMapTy &Map = State->get<StreamMap>(); 1613 for (const auto &I : Map) { 1614 SymbolRef Sym = I.first; 1615 const StreamState &SS = I.second; 1616 if (!SymReaper.isDead(Sym)) 1617 continue; 1618 if (SS.isOpened()) 1619 LeakedSyms.push_back(Sym); 1620 State = State->remove<StreamMap>(Sym); 1621 } 1622 1623 ExplodedNode *N = C.getPredecessor(); 1624 if (!LeakedSyms.empty()) 1625 N = reportLeaks(LeakedSyms, C, N); 1626 1627 C.addTransition(State, N); 1628 } 1629 1630 ProgramStateRef StreamChecker::checkPointerEscape( 1631 ProgramStateRef State, const InvalidatedSymbols &Escaped, 1632 const CallEvent *Call, PointerEscapeKind Kind) const { 1633 // Check for file-handling system call that is not handled by the checker. 1634 // FIXME: The checker should be updated to handle all system calls that take 1635 // 'FILE*' argument. These are now ignored. 1636 if (Kind == PSK_DirectEscapeOnCall && Call->isInSystemHeader()) 1637 return State; 1638 1639 for (SymbolRef Sym : Escaped) { 1640 // The symbol escaped. 1641 // From now the stream can be manipulated in unknown way to the checker, 1642 // it is not possible to handle it any more. 1643 // Optimistically, assume that the corresponding file handle will be closed 1644 // somewhere else. 1645 // Remove symbol from state so the following stream calls on this symbol are 1646 // not handled by the checker. 1647 State = State->remove<StreamMap>(Sym); 1648 } 1649 return State; 1650 } 1651 1652 //===----------------------------------------------------------------------===// 1653 // Checker registration. 1654 //===----------------------------------------------------------------------===// 1655 1656 void ento::registerStreamChecker(CheckerManager &Mgr) { 1657 Mgr.registerChecker<StreamChecker>(); 1658 } 1659 1660 bool ento::shouldRegisterStreamChecker(const CheckerManager &Mgr) { 1661 return true; 1662 } 1663 1664 void ento::registerStreamTesterChecker(CheckerManager &Mgr) { 1665 auto *Checker = Mgr.getChecker<StreamChecker>(); 1666 Checker->TestMode = true; 1667 } 1668 1669 bool ento::shouldRegisterStreamTesterChecker(const CheckerManager &Mgr) { 1670 return true; 1671 } 1672